import argparse from pathlib import Path import subprocess import os from typing import Sequence, NamedTuple import sys import shutil class BootstrapPhase(NamedTuple): ref: str nix_compiler: str win_compiler: str @property def platform_compiler(self): if os.name == 'nt': return self.win_compiler else: return self.nix_compiler BOOTSTRAP_PHASES = [ BootstrapPhase('bootstrap-p1', 'g++-8', 'cl.exe'), BootstrapPhase('bootstrap-p4', 'g++-8', 'cl.exe'), BootstrapPhase('0.0.1', 'g++-9', 'cl.exe'), ] HERE = Path(__file__).parent.absolute() PROJECT_ROOT = HERE.parent BUILD_DIR = PROJECT_ROOT / '_build' BOOTSTRAP_BASE_DIR = BUILD_DIR / '_bootstrap' PREBUILT_DIR = PROJECT_ROOT / '_prebuilt' EXE_SUFFIX = '.exe' if os.name == 'nt' else '' def _run_quiet(cmd, **kwargs) -> None: cmd = [str(s) for s in cmd] res = subprocess.run( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs, ) if res.returncode != 0: print(f'Subprocess command {cmd} failed ' f'[{res.returncode}]:\n{res.stdout.decode()}') raise subprocess.CalledProcessError(res.returncode, cmd) def _clone_bootstrap_phase(ref: str) -> Path: print(f'Clone revision: {ref}') bts_dir = BOOTSTRAP_BASE_DIR / ref if bts_dir.exists(): shutil.rmtree(bts_dir) _run_quiet([ 'git', 'clone', '--depth=1', f'--branch={ref}', f'file://{PROJECT_ROOT}', bts_dir, ]) return bts_dir def _build_bootstrap_phase(ph: BootstrapPhase, bts_dir: Path) -> None: print(f'Build revision: {ph.ref} [This may take a moment]') env = os.environ.copy() env['DDS_BOOTSTRAP_PREV_EXE'] = str(PREBUILT_DIR / F'dds{EXE_SUFFIX}') _run_quiet( [ sys.executable, '-u', str(bts_dir / 'tools/build.py'), f'--cxx={ph.platform_compiler}', ], env=env, cwd=bts_dir, ) def _pull_executable(bts_dir: Path) -> Path: prebuild_dir = (PROJECT_ROOT / '_prebuilt') prebuild_dir.mkdir(exist_ok=True) generated = list(bts_dir.glob(f'_build/dds{EXE_SUFFIX}')) assert len(generated) == 1, repr(generated) exe, = generated dest = prebuild_dir / exe.name if dest.exists(): dest.unlink() exe.rename(dest) return dest def _run_boot_phase(phase: BootstrapPhase) -> Path: bts_dir = _clone_bootstrap_phase(phase.ref) _build_bootstrap_phase(phase, bts_dir) return _pull_executable(bts_dir) def main(argv: Sequence[str]) -> int: for idx, phase in enumerate(BOOTSTRAP_PHASES): print(f'Bootstrap phase [{idx+1}/{len(BOOTSTRAP_PHASES)}]') exe = _run_boot_phase(phase) print(f'A bootstrapped DDS executable has been generated: {exe}') return 0 if __name__ == "__main__": sys.exit(main(sys.argv[1:]))