| echo Executing Build and Tests | echo Executing Build and Tests | ||||
| reg add HKLM\SYSTEM\CurrentControlSet\Control\FileSystem /v LongPathsEnabled /t REG_DWORD /d 1 /f || exit 1 | reg add HKLM\SYSTEM\CurrentControlSet\Control\FileSystem /v LongPathsEnabled /t REG_DWORD /d 1 /f || exit 1 | ||||
| python -m pip install pytest pytest-xdist || exit 1 | python -m pip install pytest pytest-xdist || exit 1 | ||||
| python -u tools/ci.py -B download --cxx cl.exe -T tools\msvc.dds || exit 1 | |||||
| python -u tools/ci.py -B build -T tools\msvc.dds || exit 1 | |||||
| displayName: Full CI | displayName: Full CI | ||||
| - publish: _build/dds.exe | - publish: _build/dds.exe | ||||
| artifact: DDS Executable - Windows VS2019 | artifact: DDS Executable - Windows VS2019 | ||||
| - script: | | - script: | | ||||
| set -eu | set -eu | ||||
| sudo apt update -y | sudo apt update -y | ||||
| sudo apt install -y python3-minimal g++-9 ccache | |||||
| sudo apt install -y python3-minimal g++-9 g++-8 ccache | |||||
| python3 -m pip install pytest pytest-xdist | python3 -m pip install pytest pytest-xdist | ||||
| displayName: Prepare System | displayName: Prepare System | ||||
| - script: python3 -u tools/ci.py -B download -T tools/gcc-9.dds | |||||
| - script: python3 -u tools/ci.py -B build -T tools/gcc-9.dds | |||||
| displayName: Full CI | displayName: Full CI | ||||
| - publish: _build/dds | - publish: _build/dds | ||||
| artifact: DDS Executable - Linux | artifact: DDS Executable - Linux | ||||
| - script: | | - script: | | ||||
| set -eu | set -eu | ||||
| python3 -m pip install pytest pytest-xdist | python3 -m pip install pytest pytest-xdist | ||||
| python3 -u tools/ci.py -B download -T tools/gcc-9.dds | |||||
| python3 -u tools/ci.py -B build -T tools/gcc-9.dds | |||||
| displayName: Build and Run Unit Tests | displayName: Build and Run Unit Tests | ||||
| - publish: _build/dds | - publish: _build/dds | ||||
| artifact: DDS Executable - macOS | artifact: DDS Executable - macOS |
| from pathlib import Path | from pathlib import Path | ||||
| import subprocess | import subprocess | ||||
| import os | import os | ||||
| from typing import Sequence | |||||
| from typing import Sequence, NamedTuple | |||||
| import sys | import sys | ||||
| import shutil | 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 = [ | BOOTSTRAP_PHASES = [ | ||||
| 'bootstrap-p1', | |||||
| 'bootstrap-p4', | |||||
| BootstrapPhase('bootstrap-p1', 'g++-8', 'cl.exe'), | |||||
| BootstrapPhase('bootstrap-p4', 'g++-8', 'cl.exe'), | |||||
| BootstrapPhase('bootstrap-p5', 'g++-9', 'cl.exe'), | |||||
| ] | ] | ||||
| HERE = Path(__file__).parent.absolute() | HERE = Path(__file__).parent.absolute() | ||||
| def _run_quiet(cmd, **kwargs) -> None: | def _run_quiet(cmd, **kwargs) -> None: | ||||
| cmd = [str(s) for s in cmd] | cmd = [str(s) for s in cmd] | ||||
| res = subprocess.run( | res = subprocess.run( | ||||
| cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs) | |||||
| cmd, | |||||
| stdout=subprocess.PIPE, | |||||
| stderr=subprocess.STDOUT, | |||||
| **kwargs, | |||||
| ) | |||||
| if res.returncode != 0: | if res.returncode != 0: | ||||
| print(f'Subprocess command {cmd} failed ' | print(f'Subprocess command {cmd} failed ' | ||||
| f'[{res.returncode}]:\n{res.stdout.decode()}') | f'[{res.returncode}]:\n{res.stdout.decode()}') | ||||
| raise subprocess.CalledProcessError(res.returncode, cmd) | raise subprocess.CalledProcessError(res.returncode, cmd) | ||||
| def _clone_bootstrap_phase(ph: str) -> Path: | |||||
| print(f'Clone revision: {ph}') | |||||
| bts_dir = BOOTSTRAP_BASE_DIR / ph | |||||
| def _clone_bootstrap_phase(ref: str) -> Path: | |||||
| print(f'Clone revision: {ref}') | |||||
| bts_dir = BOOTSTRAP_BASE_DIR / ref | |||||
| if bts_dir.exists(): | if bts_dir.exists(): | ||||
| shutil.rmtree(bts_dir) | shutil.rmtree(bts_dir) | ||||
| _run_quiet([ | _run_quiet([ | ||||
| 'git', | 'git', | ||||
| 'clone', | 'clone', | ||||
| '--depth=1', | '--depth=1', | ||||
| f'--branch={ph}', | |||||
| f'--branch={ref}', | |||||
| f'file://{PROJECT_ROOT}', | f'file://{PROJECT_ROOT}', | ||||
| bts_dir, | bts_dir, | ||||
| ]) | ]) | ||||
| return bts_dir | return bts_dir | ||||
| def _build_bootstrap_phase(ph: str, bts_dir: Path, | |||||
| args: argparse.Namespace) -> None: | |||||
| print(f'Build revision: {ph} [This may take a moment]') | |||||
| 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 = os.environ.copy() | ||||
| env['DDS_BOOTSTRAP_PREV_EXE'] = str(PREBUILT_DIR / 'dds') | |||||
| env['DDS_BOOTSTRAP_PREV_EXE'] = str(PREBUILT_DIR / F'dds{EXE_SUFFIX}') | |||||
| _run_quiet( | _run_quiet( | ||||
| [ | [ | ||||
| sys.executable, | sys.executable, | ||||
| '-u', | '-u', | ||||
| str(bts_dir / 'tools/build.py'), | str(bts_dir / 'tools/build.py'), | ||||
| f'--cxx={args.cxx}', | |||||
| f'--cxx={ph.platform_compiler}', | |||||
| ], | ], | ||||
| env=env, | env=env, | ||||
| cwd=bts_dir, | cwd=bts_dir, | ||||
| return dest | return dest | ||||
| def _run_boot_phase(phase: str, args: argparse.Namespace) -> Path: | |||||
| bts_dir = _clone_bootstrap_phase(phase) | |||||
| _build_bootstrap_phase(phase, bts_dir, args) | |||||
| 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) | return _pull_executable(bts_dir) | ||||
| def main(argv: Sequence[str]) -> int: | def main(argv: Sequence[str]) -> int: | ||||
| parser = argparse.ArgumentParser() | |||||
| parser.add_argument( | |||||
| '--cxx', help='The C++ compiler to use for the build', required=True) | |||||
| args = parser.parse_args(argv) | |||||
| for idx, phase in enumerate(BOOTSTRAP_PHASES): | for idx, phase in enumerate(BOOTSTRAP_PHASES): | ||||
| print(f'Bootstrap phase [{idx+1}/{len(BOOTSTRAP_PHASES)}]') | print(f'Bootstrap phase [{idx+1}/{len(BOOTSTRAP_PHASES)}]') | ||||
| exe = _run_boot_phase(phase, args) | |||||
| exe = _run_boot_phase(phase) | |||||
| print(f'A bootstrapped DDS executable has been generated: {exe}') | print(f'A bootstrapped DDS executable has been generated: {exe}') | ||||
| return 0 | return 0 |
| #!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
| import argparse | import argparse | ||||
| from contextlib import contextmanager | |||||
| import os | import os | ||||
| from pathlib import Path | from pathlib import Path | ||||
| from typing import Sequence | from typing import Sequence | ||||
| import subprocess | |||||
| import sys | import sys | ||||
| import shutil | import shutil | ||||
| import tempfile | |||||
| from dds_ci import paths | from dds_ci import paths | ||||
| from self_build import self_build | from self_build import self_build | ||||
| BUILD_DIR = ROOT / '_build' | BUILD_DIR = ROOT / '_build' | ||||
| @contextmanager | |||||
| def _generate_toolchain(cxx: str): | |||||
| with tempfile.NamedTemporaryFile( | |||||
| suffix='-dds-toolchain.dds', mode='wb', delete=False) as f: | |||||
| comp_id = 'GNU' | |||||
| flags = '' | |||||
| link_flags = '' | |||||
| if cxx in ('cl', 'cl.exe'): | |||||
| comp_id = 'MSVC' | |||||
| flags += '/experimental:preprocessor ' | |||||
| link_flags += 'rpcrt4.lib ' | |||||
| else: | |||||
| flags += '-fconcepts' | |||||
| flags += ' -DSPDLOG_COMPILED_LIB' | |||||
| content = f''' | |||||
| Compiler-ID: {comp_id} | |||||
| C++-Compiler: {cxx} | |||||
| C++-Version: C++17 | |||||
| Debug: True | |||||
| Flags: {flags} | |||||
| Link-Flags: {link_flags} | |||||
| ''' | |||||
| print('Using generated toolchain file: ' + content) | |||||
| f.write(content.encode('utf-8')) | |||||
| f.close() | |||||
| yield Path(f.name) | |||||
| os.unlink(f.name) | |||||
| def main(argv: Sequence[str]) -> int: | def main(argv: Sequence[str]) -> int: | ||||
| # Prior versions of this script took a --cxx argument, but we don't care anymore | |||||
| parser = argparse.ArgumentParser() | parser = argparse.ArgumentParser() | ||||
| parser.add_argument( | |||||
| '--cxx', help='Path/name of the C++ compiler to use.', required=True) | |||||
| args = parser.parse_args(argv) | |||||
| parser.add_argument('--cxx', help=argparse.SUPPRESS) | |||||
| parser.parse_args(argv) | |||||
| dds_bootstrap_env_key = 'DDS_BOOTSTRAP_PREV_EXE' | dds_bootstrap_env_key = 'DDS_BOOTSTRAP_PREV_EXE' | ||||
| if dds_bootstrap_env_key not in os.environ: | if dds_bootstrap_env_key not in os.environ: | ||||
| print(f'Using previously built DDS executable: {dds_exe}') | print(f'Using previously built DDS executable: {dds_exe}') | ||||
| self_deps_get(dds_exe, paths.SELF_TEST_REPO_DIR) | self_deps_get(dds_exe, paths.SELF_TEST_REPO_DIR) | ||||
| with _generate_toolchain(args.cxx) as tc_fpath: | |||||
| self_deps_build(dds_exe, tc_fpath, paths.SELF_TEST_REPO_DIR, | |||||
| ROOT / 'remote.dds') | |||||
| self_build(dds_exe, toolchain=tc_fpath, dds_flags=['--apps']) | |||||
| if os.name == 'nt': | |||||
| tc_fpath = ROOT / 'tools/msvc.dds' | |||||
| else: | |||||
| tc_fpath = ROOT / 'tools/gcc-9.dds' | |||||
| self_deps_build(dds_exe, str(tc_fpath), paths.SELF_TEST_REPO_DIR, | |||||
| ROOT / 'remote.dds') | |||||
| self_build(dds_exe, toolchain=str(tc_fpath), dds_flags=['--apps']) | |||||
| return 0 | return 0 | ||||
| class CIOptions(NamedTuple): | class CIOptions(NamedTuple): | ||||
| cxx: Path | |||||
| toolchain: str | toolchain: str | ||||
| skip_deps: bool | |||||
| def _do_bootstrap_build(opts: CIOptions) -> None: | def _do_bootstrap_build(opts: CIOptions) -> None: | ||||
| sys.executable, | sys.executable, | ||||
| '-u', | '-u', | ||||
| str(paths.TOOLS_DIR / 'bootstrap.py'), | str(paths.TOOLS_DIR / 'bootstrap.py'), | ||||
| f'--cxx={opts.cxx}', | |||||
| ]) | ]) | ||||
| choices=('download', 'build', 'skip'), | choices=('download', 'build', 'skip'), | ||||
| required=True, | required=True, | ||||
| ) | ) | ||||
| parser.add_argument( | |||||
| '--cxx', help='The name/path of the C++ compiler to use.') | |||||
| parser.add_argument( | parser.add_argument( | ||||
| '--toolchain', | '--toolchain', | ||||
| '-T', | '-T', | ||||
| help='The toolchain to use for the CI process', | help='The toolchain to use for the CI process', | ||||
| required=True) | required=True) | ||||
| parser.add_argument( | |||||
| '--skip-deps', | |||||
| action='store_true', | |||||
| help='If specified, will skip getting and building ' | |||||
| 'dependencies. (They must already be present)') | |||||
| args = parser.parse_args(argv) | args = parser.parse_args(argv) | ||||
| opts = CIOptions( | |||||
| cxx=Path(args.cxx or 'unspecified'), | |||||
| toolchain=args.toolchain, | |||||
| skip_deps=args.skip_deps) | |||||
| opts = CIOptions(toolchain=args.toolchain) | |||||
| if args.bootstrap_with == 'build': | if args.bootstrap_with == 'build': | ||||
| if args.cxx is None: | |||||
| raise RuntimeError( | |||||
| '`--cxx` must be given when using `--bootstrap-with=build`') | |||||
| _do_bootstrap_build(opts) | _do_bootstrap_build(opts) | ||||
| elif args.bootstrap_with == 'download': | elif args.bootstrap_with == 'download': | ||||
| _do_bootstrap_download() | _do_bootstrap_download() | ||||
| else: | else: | ||||
| assert False, 'impossible' | assert False, 'impossible' | ||||
| cat_path = paths.BUILD_DIR / 'catalog.db' | |||||
| ci_repo_dir = paths.BUILD_DIR / '_ci-repo' | ci_repo_dir = paths.BUILD_DIR / '_ci-repo' | ||||
| if not opts.skip_deps: | |||||
| if ci_repo_dir.exists(): | |||||
| shutil.rmtree(ci_repo_dir) | |||||
| self_deps_get(paths.PREBUILT_DDS, ci_repo_dir) | |||||
| self_deps_build(paths.PREBUILT_DDS, opts.toolchain, ci_repo_dir, | |||||
| paths.PROJECT_ROOT / 'remote.dds') | |||||
| if ci_repo_dir.exists(): | |||||
| shutil.rmtree(ci_repo_dir) | |||||
| proc.check_run([ | |||||
| paths.PREBUILT_DDS, | |||||
| 'catalog', | |||||
| 'import', | |||||
| ('--catalog', cat_path), | |||||
| ('--json', paths.PROJECT_ROOT / 'catalog.json'), | |||||
| ]) | |||||
| self_build( | self_build( | ||||
| paths.PREBUILT_DDS, | paths.PREBUILT_DDS, | ||||
| toolchain=opts.toolchain, | toolchain=opts.toolchain, | ||||
| dds_flags=['--warnings', '--tests', '--apps']) | |||||
| dds_flags=[ | |||||
| ('--catalog', cat_path), | |||||
| ('--repo-dir', ci_repo_dir), | |||||
| ]) | |||||
| print('Main build PASSED!') | print('Main build PASSED!') | ||||
| cat_path = paths.BUILD_DIR / 'catalog.db' | |||||
| proc.check_run([ | proc.check_run([ | ||||
| paths.CUR_BUILT_DDS, | paths.CUR_BUILT_DDS, | ||||
| 'catalog', | 'catalog', |