You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

bootstrap.py 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. import enum
  2. from pathlib import Path
  3. import os
  4. from contextlib import contextmanager
  5. from typing import Iterator, Optional, Mapping
  6. import sys
  7. import urllib.request
  8. import platform
  9. import shutil
  10. from . import paths, proc
  11. from .dds import DDSWrapper
  12. from .paths import new_tempdir
  13. class BootstrapMode(enum.Enum):
  14. """How should be bootstrap our prior DDS executable?"""
  15. #: Downlaod one from GitHub
  16. Download = 'download'
  17. #: Build one from source
  18. Build = 'build'
  19. #: Skip bootstrapping. Assume it already exists.
  20. Skip = 'skip'
  21. #: If the prior executable exists, skip, otherwise download
  22. Lazy = 'lazy'
  23. def _do_bootstrap_download() -> Path:
  24. filename = {
  25. 'win32': 'dds-win-x64.exe',
  26. 'linux': 'dds-linux-x64',
  27. 'darwin': 'dds-macos-x64',
  28. 'freebsd11': 'dds-freebsd-x64',
  29. 'freebsd12': 'dds-freebsd-x64',
  30. }.get(sys.platform)
  31. if filename is None:
  32. raise RuntimeError(f'We do not have a prebuilt DDS binary for the "{sys.platform}" platform')
  33. url = f'https://github.com/vector-of-bool/dds/releases/download/0.1.0-alpha.6/{filename}'
  34. print(f'Downloading prebuilt DDS executable: {url}')
  35. stream = urllib.request.urlopen(url)
  36. paths.PREBUILT_DDS.parent.mkdir(exist_ok=True, parents=True)
  37. with paths.PREBUILT_DDS.open('wb') as fd:
  38. while True:
  39. buf = stream.read(1024 * 4)
  40. if not buf:
  41. break
  42. fd.write(buf)
  43. if sys.platform != 'win32':
  44. # Mark the binary executable. By default it won't be
  45. mode = paths.PREBUILT_DDS.stat().st_mode
  46. mode |= 0b001_001_001
  47. paths.PREBUILT_DDS.chmod(mode)
  48. return paths.PREBUILT_DDS
  49. @contextmanager
  50. def pin_exe(fpath: Path) -> Iterator[Path]:
  51. """
  52. Create a copy of 'fpath' at an unspecified location, and yield that path.
  53. This is needed if the executable would overwrite itself.
  54. """
  55. with new_tempdir() as tdir:
  56. tfile = tdir / 'previous-dds.exe'
  57. shutil.copy2(fpath, tfile)
  58. yield tfile
  59. @contextmanager
  60. def get_bootstrap_exe(mode: BootstrapMode) -> Iterator[DDSWrapper]:
  61. """Context manager that yields a DDSWrapper around a prior 'dds' executable"""
  62. if mode is BootstrapMode.Lazy:
  63. f = paths.PREBUILT_DDS
  64. if not f.exists():
  65. _do_bootstrap_download()
  66. elif mode is BootstrapMode.Download:
  67. f = _do_bootstrap_download()
  68. elif mode is BootstrapMode.Build:
  69. f = _do_bootstrap_build()
  70. elif mode is BootstrapMode.Skip:
  71. f = paths.PREBUILT_DDS
  72. with pin_exe(f) as dds:
  73. yield DDSWrapper(dds)
  74. def _do_bootstrap_build() -> Path:
  75. return _bootstrap_p6()
  76. def _bootstrap_p6() -> Path:
  77. prev_dds = _bootstrap_alpha_4()
  78. p6_dir = paths.PREBUILT_DIR / 'p6'
  79. ret_dds = _dds_in(p6_dir)
  80. if ret_dds.exists():
  81. return ret_dds
  82. _clone_self_at(p6_dir, '0.1.0-alpha.6-bootstrap')
  83. tc = 'msvc-rel.jsonc' if platform.system() == 'Windows' else 'gcc-9-rel.jsonc'
  84. catalog_arg = f'--catalog={p6_dir}/_catalog.db'
  85. repo_arg = f'--repo-dir={p6_dir}/_repo'
  86. proc.check_run(
  87. [prev_dds, 'catalog', 'import', catalog_arg, '--json=old-catalog.json'],
  88. cwd=p6_dir,
  89. )
  90. proc.check_run(
  91. [
  92. prev_dds,
  93. 'build',
  94. catalog_arg,
  95. repo_arg,
  96. ('--toolchain', p6_dir / 'tools' / tc),
  97. ],
  98. cwd=p6_dir,
  99. )
  100. return ret_dds
  101. def _bootstrap_alpha_4() -> Path:
  102. prev_dds = _bootstrap_alpha_3()
  103. a4_dir = paths.PREBUILT_DIR / 'alpha-4'
  104. ret_dds = _dds_in(a4_dir)
  105. if ret_dds.exists():
  106. return ret_dds
  107. _clone_self_at(a4_dir, '0.1.0-alpha.4')
  108. build_py = a4_dir / 'tools/build.py'
  109. proc.check_run(
  110. [
  111. sys.executable,
  112. '-u',
  113. build_py,
  114. ],
  115. env=_prev_dds_env(prev_dds),
  116. cwd=a4_dir,
  117. )
  118. return ret_dds
  119. def _bootstrap_alpha_3() -> Path:
  120. prev_dds = _bootstrap_p5()
  121. a3_dir = paths.PREBUILT_DIR / 'alpha-3'
  122. ret_dds = _dds_in(a3_dir)
  123. if ret_dds.exists():
  124. return ret_dds
  125. _clone_self_at(a3_dir, '0.1.0-alpha.3')
  126. build_py = a3_dir / 'tools/build.py'
  127. proc.check_run(
  128. [
  129. sys.executable,
  130. '-u',
  131. build_py,
  132. ],
  133. env=_prev_dds_env(prev_dds),
  134. cwd=a3_dir,
  135. )
  136. return ret_dds
  137. def _bootstrap_p5() -> Path:
  138. prev_dds = _bootstrap_p4()
  139. p5_dir = paths.PREBUILT_DIR / 'p5'
  140. ret_dds = _dds_in(p5_dir)
  141. if ret_dds.exists():
  142. return ret_dds
  143. _clone_self_at(p5_dir, 'bootstrap-p5.2')
  144. build_py = p5_dir / 'tools/build.py'
  145. proc.check_run(
  146. [
  147. sys.executable,
  148. '-u',
  149. build_py,
  150. ],
  151. env=_prev_dds_env(prev_dds),
  152. cwd=p5_dir,
  153. )
  154. return ret_dds
  155. def _bootstrap_p4() -> Path:
  156. prev_dds = _bootstrap_p1()
  157. p4_dir = paths.PREBUILT_DIR / 'p4'
  158. p4_dir.mkdir(exist_ok=True, parents=True)
  159. ret_dds = _dds_in(p4_dir)
  160. if ret_dds.exists():
  161. return ret_dds
  162. _clone_self_at(p4_dir, 'bootstrap-p4.2')
  163. build_py = p4_dir / 'tools/build.py'
  164. proc.check_run(
  165. [
  166. sys.executable,
  167. '-u',
  168. build_py,
  169. '--cxx=cl.exe' if platform.system() == 'Windows' else '--cxx=g++-8',
  170. ],
  171. env=_prev_dds_env(prev_dds),
  172. )
  173. return ret_dds
  174. def _bootstrap_p1() -> Path:
  175. p1_dir = paths.PREBUILT_DIR / 'p1'
  176. p1_dir.mkdir(exist_ok=True, parents=True)
  177. ret_dds = _dds_in(p1_dir)
  178. if ret_dds.exists():
  179. return ret_dds
  180. _clone_self_at(p1_dir, 'bootstrap-p1.2')
  181. build_py = p1_dir / 'tools/build.py'
  182. proc.check_run([
  183. sys.executable,
  184. '-u',
  185. build_py,
  186. '--cxx=cl.exe' if platform.system() == 'Windows' else '--cxx=g++-8',
  187. ])
  188. _build_prev(p1_dir)
  189. return ret_dds
  190. def _clone_self_at(dir: Path, ref: str) -> None:
  191. if dir.is_dir():
  192. shutil.rmtree(dir)
  193. dir.mkdir(exist_ok=True, parents=True)
  194. proc.check_run(['git', 'clone', '-qq', paths.PROJECT_ROOT, f'--branch={ref}', dir])
  195. def _dds_in(dir: Path) -> Path:
  196. return dir.joinpath('_build/dds' + paths.EXE_SUFFIX)
  197. def _prev_dds_env(dds: Path) -> Mapping[str, str]:
  198. env = os.environ.copy()
  199. env.update({'DDS_BOOTSTRAP_PREV_EXE': str(dds)})
  200. return env
  201. def _build_prev(dir: Path, prev_dds: Optional[Path] = None) -> None:
  202. build_py = dir / 'tools/build.py'
  203. proc.check_run(
  204. [
  205. sys.executable,
  206. '-u',
  207. build_py,
  208. '--cxx=cl.exe' if platform.system() == 'Windows' else '--cxx=g++-8',
  209. ],
  210. cwd=dir,
  211. env=None if prev_dds is None else os.environ.clone().update({'DDS_BOOTSTRAP_PREV_EXE',
  212. str(prev_dds)}),
  213. )