|
|
|
|
|
|
|
|
#!/usr/bin/env python3 |
|
|
#!/usr/bin/env python3 |
|
|
|
|
|
|
|
|
import argparse |
|
|
import argparse |
|
|
|
|
|
import os |
|
|
from pathlib import Path |
|
|
from pathlib import Path |
|
|
import multiprocessing |
|
|
import multiprocessing |
|
|
import itertools |
|
|
import itertools |
|
|
from concurrent.futures import ThreadPoolExecutor |
|
|
from concurrent.futures import ThreadPoolExecutor |
|
|
from typing import Sequence, Iterable, Dict, Tuple |
|
|
|
|
|
|
|
|
from typing import Sequence, Iterable, Dict, Tuple,List |
|
|
import subprocess |
|
|
import subprocess |
|
|
import time |
|
|
import time |
|
|
import sys |
|
|
import sys |
|
|
|
|
|
|
|
|
INCLUDE_DIRS = [ |
|
|
INCLUDE_DIRS = [ |
|
|
'external/taywee-args/include', |
|
|
'external/taywee-args/include', |
|
|
'external/spdlog/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]: |
|
|
def _compile_src(cxx: Path, cpp_file: Path) -> Tuple[Path, Path]: |
|
|
build_dir = HERE_DIR / '_build' |
|
|
build_dir = HERE_DIR / '_build' |
|
|
src_dir = HERE_DIR / 'src' |
|
|
src_dir = HERE_DIR / 'src' |
|
|
relpath = cpp_file.relative_to(src_dir) |
|
|
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) |
|
|
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}' |
|
|
msg = f'Compile C++ file: {cpp_file}' |
|
|
print(msg) |
|
|
print(msg) |
|
|
start = time.time() |
|
|
start = time.time() |
|
|
|
|
|
|
|
|
raise RuntimeError( |
|
|
raise RuntimeError( |
|
|
f'Compile command ({cmd}) failed for {cpp_file}:\n{res.stdout.decode()}' |
|
|
f'Compile command ({cmd}) failed for {cpp_file}:\n{res.stdout.decode()}' |
|
|
) |
|
|
) |
|
|
|
|
|
if res.stdout: |
|
|
|
|
|
print(res.stdout.decode()) |
|
|
end = time.time() |
|
|
end = time.time() |
|
|
print(f'{msg} - Done: {end - start:.2}s') |
|
|
print(f'{msg} - Done: {end - start:.2}s') |
|
|
return cpp_file, obj_path |
|
|
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 { |
|
|
return { |
|
|
src: obj |
|
|
src: obj |
|
|
for src, obj in pool.map(lambda s: _compile_src(cxx, s), sources) |
|
|
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: |
|
|
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(): |
|
|
if lib_file.exists(): |
|
|
lib_file.unlink() |
|
|
lib_file.unlink() |
|
|
print(f'Creating static library {lib_file}') |
|
|
print(f'Creating static library {lib_file}') |
|
|
|
|
|
|
|
|
return lib_file |
|
|
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: |
|
|
def link_exe(cxx: Path, obj: Path, lib: Path, *, out: Path = None) -> Path: |
|
|
if out is None: |
|
|
if out is None: |
|
|
basename = obj.stem |
|
|
basename = obj.stem |
|
|
|
|
|
|
|
|
out.parent.mkdir(exist_ok=True, parents=True) |
|
|
out.parent.mkdir(exist_ok=True, parents=True) |
|
|
|
|
|
|
|
|
print(f'Linking executable {out}') |
|
|
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 |
|
|
return out |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'--test', action='store_true', help='Build and run tests') |
|
|
'--test', action='store_true', help='Build and run tests') |
|
|
parser.add_argument( |
|
|
parser.add_argument( |
|
|
'--cxx', help='Path/name of the C++ compiler to use.', required=True) |
|
|
'--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) |
|
|
args = parser.parse_args(argv) |
|
|
|
|
|
|
|
|
all_sources = set(HERE_DIR.glob('src/**/*.cpp')) |
|
|
all_sources = set(HERE_DIR.glob('src/**/*.cpp')) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lib_sources = (all_sources - test_sources) - main_sources |
|
|
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) |
|
|
lib = make_library(objects[p] for p in lib_sources) |
|
|
|
|
|
|
|
|
test_objs = (objects[p] for p in test_sources) |
|
|
test_objs = (objects[p] for p in test_sources) |
|
|
pool = ThreadPoolExecutor(multiprocessing.cpu_count() + 2) |
|
|
|
|
|
|
|
|
pool = ThreadPoolExecutor(args.jobs) |
|
|
test_exes = list( |
|
|
test_exes = list( |
|
|
pool.map(lambda o: link_exe(Path(args.cxx), o, lib), test_objs)) |
|
|
pool.map(lambda o: link_exe(Path(args.cxx), o, lib), test_objs)) |
|
|
|
|
|
|