bootstrap.py 2.5KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import enum
  2. from pathlib import Path
  3. from contextlib import contextmanager
  4. from typing import Iterator
  5. import sys
  6. import urllib.request
  7. import shutil
  8. from . import paths
  9. from .dds import DDSWrapper
  10. from .paths import new_tempdir
  11. class BootstrapMode(enum.Enum):
  12. """How should be bootstrap our prior DDS executable?"""
  13. #: Downlaod one from GitHub
  14. Download = 'download'
  15. #: Build one from source
  16. Build = 'build'
  17. #: Skip bootstrapping. Assume it already exists.
  18. Skip = 'skip'
  19. #: If the prior executable exists, skip, otherwise download
  20. Lazy = 'lazy'
  21. def _do_bootstrap_download() -> Path:
  22. filename = {
  23. 'win32': 'dds-win-x64.exe',
  24. 'linux': 'dds-linux-x64',
  25. 'darwin': 'dds-macos-x64',
  26. 'freebsd11': 'dds-freebsd-x64',
  27. 'freebsd12': 'dds-freebsd-x64',
  28. }.get(sys.platform)
  29. if filename is None:
  30. raise RuntimeError(f'We do not have a prebuilt DDS binary for the "{sys.platform}" platform')
  31. url = f'https://github.com/vector-of-bool/dds/releases/download/0.1.0-alpha.4/{filename}'
  32. print(f'Downloading prebuilt DDS executable: {url}')
  33. stream = urllib.request.urlopen(url)
  34. paths.PREBUILT_DDS.parent.mkdir(exist_ok=True, parents=True)
  35. with paths.PREBUILT_DDS.open('wb') as fd:
  36. while True:
  37. buf = stream.read(1024 * 4)
  38. if not buf:
  39. break
  40. fd.write(buf)
  41. if sys.platform != 'win32':
  42. # Mark the binary executable. By default it won't be
  43. mode = paths.PREBUILT_DDS.stat().st_mode
  44. mode |= 0b001_001_001
  45. paths.PREBUILT_DDS.chmod(mode)
  46. return paths.PREBUILT_DDS
  47. @contextmanager
  48. def pin_exe(fpath: Path) -> Iterator[Path]:
  49. """
  50. Create a copy of 'fpath' at an unspecified location, and yield that path.
  51. This is needed if the executable would overwrite itself.
  52. """
  53. with new_tempdir() as tdir:
  54. tfile = tdir / 'previous-dds.exe'
  55. shutil.copy2(fpath, tfile)
  56. yield tfile
  57. @contextmanager
  58. def get_bootstrap_exe(mode: BootstrapMode) -> Iterator[DDSWrapper]:
  59. """Context manager that yields a DDSWrapper around a prior 'dds' executable"""
  60. if mode is BootstrapMode.Lazy:
  61. f = paths.PREBUILT_DDS
  62. if not f.exists():
  63. _do_bootstrap_download()
  64. elif mode is BootstrapMode.Download:
  65. f = _do_bootstrap_download()
  66. elif mode is BootstrapMode.Build:
  67. f = _do_bootstrap_build() # type: ignore # TODO
  68. elif mode is BootstrapMode.Skip:
  69. f = paths.PREBUILT_DDS
  70. with pin_exe(f) as dds:
  71. yield DDSWrapper(dds)