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', |