| @@ -1,11 +1,12 @@ | |||
| #!/usr/bin/env python3 | |||
| import argparse | |||
| import os | |||
| from pathlib import Path | |||
| import multiprocessing | |||
| import itertools | |||
| from concurrent.futures import ThreadPoolExecutor | |||
| from typing import Sequence, Iterable, Dict, Tuple | |||
| from typing import Sequence, Iterable, Dict, Tuple,List | |||
| import subprocess | |||
| import time | |||
| import sys | |||
| @@ -15,36 +16,69 @@ HERE_DIR = Path(__file__).parent.absolute() | |||
| INCLUDE_DIRS = [ | |||
| 'external/taywee-args/include', | |||
| 'external/spdlog/include', | |||
| 'external/wil/include', | |||
| ] | |||
| def is_msvc(cxx: Path) -> bool: | |||
| return (not 'clang' in cxx.name) and 'cl' in cxx.name | |||
| def _create_compile_command(cxx: Path, cpp_file: Path, obj_file: Path) -> List[str]: | |||
| if not is_msvc(cxx): | |||
| cmd = [ | |||
| str(cxx), | |||
| f'-I{HERE_DIR / "src"}', | |||
| '-std=c++17', | |||
| '-static', | |||
| '-Wall', | |||
| '-Wextra', | |||
| '-Werror', | |||
| '-Wshadow', | |||
| '-Wconversion', | |||
| '-fdiagnostics-color', | |||
| '-pthread', | |||
| '-g', | |||
| '-c', | |||
| '-O0', | |||
| str(cpp_file), | |||
| f'-o{obj_file}', | |||
| ] | |||
| cmd.extend( | |||
| itertools.chain.from_iterable( | |||
| ('-isystem', str(HERE_DIR / subdir)) for subdir in INCLUDE_DIRS)) | |||
| return cmd | |||
| else: | |||
| cmd = [ | |||
| str(cxx), | |||
| # '/O2', | |||
| '/Od', | |||
| '/Z7', | |||
| '/DEBUG', | |||
| '/W4', | |||
| '/WX', | |||
| '/MT', | |||
| '/nologo', | |||
| # '/wd2220', | |||
| '/EHsc', | |||
| '/std:c++latest', | |||
| f'/I{HERE_DIR / "src"}', | |||
| str(cpp_file), | |||
| '/c', | |||
| f'/Fo{obj_file}', | |||
| ] | |||
| cmd.extend( | |||
| f'/I{HERE_DIR / subdir}' for subdir in INCLUDE_DIRS) | |||
| return cmd | |||
| def _compile_src(cxx: Path, cpp_file: Path) -> Tuple[Path, Path]: | |||
| build_dir = HERE_DIR / '_build' | |||
| src_dir = HERE_DIR / 'src' | |||
| relpath = cpp_file.relative_to(src_dir) | |||
| obj_path = build_dir / relpath.with_name(relpath.name + '.o') | |||
| obj_path = build_dir / relpath.with_name(relpath.name + ('.obj' if is_msvc(cxx) else '.o')) | |||
| obj_path.parent.mkdir(exist_ok=True, parents=True) | |||
| cmd = [ | |||
| cxx, | |||
| '-std=c++17', | |||
| '-static', | |||
| '-Wall', | |||
| '-Wextra', | |||
| '-Werror', | |||
| '-Wshadow', | |||
| '-Wconversion', | |||
| '-fdiagnostics-color', | |||
| '-pthread', | |||
| '-g', | |||
| '-c', | |||
| '-O0', | |||
| f'-I{src_dir}', | |||
| cpp_file, | |||
| f'-o{obj_path}', | |||
| ] | |||
| cmd.extend( | |||
| itertools.chain.from_iterable( | |||
| ('-isystem', HERE_DIR / subdir) for subdir in INCLUDE_DIRS)) | |||
| cmd = _create_compile_command(cxx, cpp_file, obj_path) | |||
| msg = f'Compile C++ file: {cpp_file}' | |||
| print(msg) | |||
| start = time.time() | |||
| @@ -57,23 +91,34 @@ def _compile_src(cxx: Path, cpp_file: Path) -> Tuple[Path, Path]: | |||
| raise RuntimeError( | |||
| f'Compile command ({cmd}) failed for {cpp_file}:\n{res.stdout.decode()}' | |||
| ) | |||
| if res.stdout: | |||
| print(res.stdout.decode()) | |||
| end = time.time() | |||
| print(f'{msg} - Done: {end - start:.2}s') | |||
| return cpp_file, obj_path | |||
| def compile_sources(cxx: Path, sources: Iterable[Path]) -> Dict[Path, Path]: | |||
| pool = ThreadPoolExecutor(multiprocessing.cpu_count() + 2) | |||
| def compile_sources(cxx: Path, sources: Iterable[Path], *, jobs: int) -> Dict[Path, Path]: | |||
| pool = ThreadPoolExecutor(jobs) | |||
| return { | |||
| src: obj | |||
| for src, obj in pool.map(lambda s: _compile_src(cxx, s), sources) | |||
| } | |||
| def _create_archive_command(objects: Iterable[Path]) -> List[str]: | |||
| if os.name == 'nt': | |||
| lib_file = HERE_DIR / '_build/libddslim.lib' | |||
| cmd = ['lib', '/nologo', f'/OUT:{lib_file}', *map(str, objects)] | |||
| return lib_file, cmd | |||
| else: | |||
| lib_file = HERE_DIR / '_build/libddslim.a' | |||
| cmd = ['ar', 'rsc', str(lib_file), *objects] | |||
| return lib_file, cmd | |||
| def make_library(objects: Iterable[Path]) -> Path: | |||
| lib_file = HERE_DIR / '_build/libddslim.a' | |||
| cmd = ['ar', 'rsc', lib_file] | |||
| cmd.extend(objects) | |||
| lib_file, cmd = _create_archive_command(objects) | |||
| if lib_file.exists(): | |||
| lib_file.unlink() | |||
| print(f'Creating static library {lib_file}') | |||
| @@ -81,6 +126,36 @@ def make_library(objects: Iterable[Path]) -> Path: | |||
| return lib_file | |||
| def _create_exe_link_command(cxx: Path, obj: Path, lib: Path, out: Path) -> List[str]: | |||
| if not is_msvc(cxx): | |||
| return [ | |||
| str(cxx), | |||
| '-static', | |||
| '-pthread', | |||
| # See: https://stackoverflow.com/questions/35116327/when-g-static-link-pthread-cause-segmentation-fault-why | |||
| '-Wl,--whole-archive', | |||
| '-lpthread', | |||
| '-Wl,--no-whole-archive', | |||
| str(obj), | |||
| str(lib), | |||
| '-lstdc++fs', | |||
| f'-o{out}', | |||
| ] | |||
| else: | |||
| return [ | |||
| str(cxx), | |||
| '/nologo', | |||
| '/W4', | |||
| '/WX', | |||
| '/MT', | |||
| '/Z7', | |||
| '/DEBUG', | |||
| f'/Fe{out}', | |||
| str(lib), | |||
| str(obj), | |||
| ] | |||
| def link_exe(cxx: Path, obj: Path, lib: Path, *, out: Path = None) -> Path: | |||
| if out is None: | |||
| basename = obj.stem | |||
| @@ -88,19 +163,8 @@ def link_exe(cxx: Path, obj: Path, lib: Path, *, out: Path = None) -> Path: | |||
| out.parent.mkdir(exist_ok=True, parents=True) | |||
| print(f'Linking executable {out}') | |||
| subprocess.check_call([ | |||
| cxx, | |||
| '-static', | |||
| '-pthread', | |||
| # See: https://stackoverflow.com/questions/35116327/when-g-static-link-pthread-cause-segmentation-fault-why | |||
| '-Wl,--whole-archive', | |||
| '-lpthread', | |||
| '-Wl,--no-whole-archive', | |||
| obj, | |||
| lib, | |||
| '-lstdc++fs', | |||
| f'-o{out}', | |||
| ]) | |||
| cmd = _create_exe_link_command(cxx, obj, lib, out) | |||
| subprocess.check_call(cmd) | |||
| return out | |||
| @@ -115,6 +179,7 @@ def main(argv: Sequence[str]) -> int: | |||
| '--test', action='store_true', help='Build and run tests') | |||
| parser.add_argument( | |||
| '--cxx', help='Path/name of the C++ compiler to use.', required=True) | |||
| parser.add_argument('--jobs', '-j', type=int, help='Set number of parallel build threads', default=multiprocessing.cpu_count() + 2) | |||
| args = parser.parse_args(argv) | |||
| all_sources = set(HERE_DIR.glob('src/**/*.cpp')) | |||
| @@ -123,12 +188,12 @@ def main(argv: Sequence[str]) -> int: | |||
| lib_sources = (all_sources - test_sources) - main_sources | |||
| objects = compile_sources(Path(args.cxx), all_sources) | |||
| objects = compile_sources(Path(args.cxx), all_sources, jobs=args.jobs) | |||
| lib = make_library(objects[p] for p in lib_sources) | |||
| test_objs = (objects[p] for p in test_sources) | |||
| pool = ThreadPoolExecutor(multiprocessing.cpu_count() + 2) | |||
| pool = ThreadPoolExecutor(args.jobs) | |||
| test_exes = list( | |||
| pool.map(lambda o: link_exe(Path(args.cxx), o, lib), test_objs)) | |||