|
|
@@ -6,7 +6,7 @@ from pathlib import Path |
|
|
|
import multiprocessing |
|
|
|
import itertools |
|
|
|
from concurrent.futures import ThreadPoolExecutor |
|
|
|
from typing import Sequence, Iterable, Dict, Tuple,List |
|
|
|
from typing import Sequence, Iterable, Dict, Tuple, List, NamedTuple |
|
|
|
import subprocess |
|
|
|
import time |
|
|
|
import sys |
|
|
@@ -20,17 +20,32 @@ INCLUDE_DIRS = [ |
|
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
class BuildOptions(NamedTuple): |
|
|
|
cxx: Path |
|
|
|
jobs: int |
|
|
|
static: bool |
|
|
|
debug: bool |
|
|
|
|
|
|
|
@property |
|
|
|
def is_msvc(self) -> bool: |
|
|
|
return is_msvc(self.cxx) |
|
|
|
|
|
|
|
@property |
|
|
|
def obj_suffix(self) -> str: |
|
|
|
return '.obj' if self.is_msvc else '.o' |
|
|
|
|
|
|
|
|
|
|
|
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): |
|
|
|
def _create_compile_command(opts: BuildOptions, cpp_file: Path, |
|
|
|
obj_file: Path) -> List[str]: |
|
|
|
if not opts.is_msvc: |
|
|
|
cmd = [ |
|
|
|
str(cxx), |
|
|
|
str(opts.cxx), |
|
|
|
f'-I{HERE_DIR / "src"}', |
|
|
|
'-std=c++17', |
|
|
|
'-static', |
|
|
|
'-Wall', |
|
|
|
'-Wextra', |
|
|
|
'-Werror', |
|
|
@@ -38,28 +53,26 @@ def _create_compile_command(cxx: Path, cpp_file: Path, obj_file: Path) -> List[s |
|
|
|
'-Wconversion', |
|
|
|
'-fdiagnostics-color', |
|
|
|
'-pthread', |
|
|
|
'-g', |
|
|
|
'-c', |
|
|
|
'-O0', |
|
|
|
str(cpp_file), |
|
|
|
f'-o{obj_file}', |
|
|
|
] |
|
|
|
if opts.static: |
|
|
|
cmd.append('-static') |
|
|
|
if opts.debug: |
|
|
|
cmd.extend(('-g', '-O0')) |
|
|
|
else: |
|
|
|
cmd.append('-O2') |
|
|
|
cmd.extend( |
|
|
|
itertools.chain.from_iterable( |
|
|
|
('-isystem', str(HERE_DIR / subdir)) for subdir in INCLUDE_DIRS)) |
|
|
|
itertools.chain.from_iterable(('-isystem', str(HERE_DIR / subdir)) |
|
|
|
for subdir in INCLUDE_DIRS)) |
|
|
|
return cmd |
|
|
|
else: |
|
|
|
cmd = [ |
|
|
|
str(cxx), |
|
|
|
# '/O2', |
|
|
|
'/Od', |
|
|
|
'/Z7', |
|
|
|
'/DEBUG', |
|
|
|
str(opts.cxx), |
|
|
|
'/W4', |
|
|
|
'/WX', |
|
|
|
'/MT', |
|
|
|
'/nologo', |
|
|
|
# '/wd2220', |
|
|
|
'/EHsc', |
|
|
|
'/std:c++latest', |
|
|
|
f'/I{HERE_DIR / "src"}', |
|
|
@@ -67,18 +80,25 @@ def _create_compile_command(cxx: Path, cpp_file: Path, obj_file: Path) -> List[s |
|
|
|
'/c', |
|
|
|
f'/Fo{obj_file}', |
|
|
|
] |
|
|
|
cmd.extend( |
|
|
|
f'/I{HERE_DIR / subdir}' for subdir in INCLUDE_DIRS) |
|
|
|
if opts.debug: |
|
|
|
cmd.extend(('/Od', '/DEBUG', '/Z7')) |
|
|
|
else: |
|
|
|
cmd.append('/O2') |
|
|
|
if opts.static: |
|
|
|
cmd.append('/MT') |
|
|
|
else: |
|
|
|
cmd.append('/MD') |
|
|
|
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(opts: BuildOptions, 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 + ('.obj' if is_msvc(cxx) else '.o')) |
|
|
|
obj_path = build_dir / relpath.with_name(relpath.name + opts.obj_suffix) |
|
|
|
obj_path.parent.mkdir(exist_ok=True, parents=True) |
|
|
|
cmd = _create_compile_command(cxx, cpp_file, obj_path) |
|
|
|
cmd = _create_compile_command(opts, cpp_file, obj_path) |
|
|
|
msg = f'Compile C++ file: {cpp_file}' |
|
|
|
print(msg) |
|
|
|
start = time.time() |
|
|
@@ -98,27 +118,29 @@ def _compile_src(cxx: Path, cpp_file: Path) -> Tuple[Path, Path]: |
|
|
|
return cpp_file, obj_path |
|
|
|
|
|
|
|
|
|
|
|
def compile_sources(cxx: Path, sources: Iterable[Path], *, jobs: int) -> Dict[Path, Path]: |
|
|
|
pool = ThreadPoolExecutor(jobs) |
|
|
|
def compile_sources(opts: BuildOptions, |
|
|
|
sources: Iterable[Path]) -> Dict[Path, Path]: |
|
|
|
pool = ThreadPoolExecutor(opts.jobs) |
|
|
|
return { |
|
|
|
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(opts, s), sources) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
def _create_archive_command(objects: Iterable[Path]) -> List[str]: |
|
|
|
if os.name == 'nt': |
|
|
|
def _create_archive_command(opts: BuildOptions, |
|
|
|
objects: Iterable[Path]) -> Tuple[Path, List[str]]: |
|
|
|
if opts.is_msvc: |
|
|
|
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] |
|
|
|
cmd = ['ar', 'rsc', str(lib_file), *map(str, objects)] |
|
|
|
return lib_file, cmd |
|
|
|
|
|
|
|
|
|
|
|
def make_library(objects: Iterable[Path]) -> Path: |
|
|
|
lib_file, cmd = _create_archive_command(objects) |
|
|
|
def make_library(opts: BuildOptions, objects: Iterable[Path]) -> Path: |
|
|
|
lib_file, cmd = _create_archive_command(opts, objects) |
|
|
|
if lib_file.exists(): |
|
|
|
lib_file.unlink() |
|
|
|
print(f'Creating static library {lib_file}') |
|
|
@@ -126,24 +148,29 @@ 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', |
|
|
|
def _create_exe_link_command(opts: BuildOptions, obj: Path, lib: Path, |
|
|
|
out: Path) -> List[str]: |
|
|
|
if not opts.is_msvc: |
|
|
|
cmd = [ |
|
|
|
str(opts.cxx), |
|
|
|
'-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}', |
|
|
|
] |
|
|
|
if opts.static: |
|
|
|
cmd.extend(( |
|
|
|
'-static', |
|
|
|
# See: https://stackoverflow.com/questions/35116327/when-g-static-link-pthread-cause-segmentation-fault-why |
|
|
|
'-Wl,--whole-archive', |
|
|
|
'-lpthread', |
|
|
|
'-Wl,--no-whole-archive', |
|
|
|
)) |
|
|
|
return cmd |
|
|
|
else: |
|
|
|
return [ |
|
|
|
str(cxx), |
|
|
|
cmd = [ |
|
|
|
str(opts.cxx), |
|
|
|
'/nologo', |
|
|
|
'/W4', |
|
|
|
'/WX', |
|
|
@@ -154,16 +181,24 @@ def _create_exe_link_command(cxx: Path, obj: Path, lib: Path, out: Path) -> List |
|
|
|
str(lib), |
|
|
|
str(obj), |
|
|
|
] |
|
|
|
if opts.debug: |
|
|
|
cmd.append('/DEBUG') |
|
|
|
if opts.static: |
|
|
|
cmd.append('/MT') |
|
|
|
else: |
|
|
|
cmd.append('/MD') |
|
|
|
return cmd |
|
|
|
|
|
|
|
|
|
|
|
def link_exe(cxx: Path, obj: Path, lib: Path, *, out: Path = None) -> Path: |
|
|
|
def link_exe(opts: BuildOptions, obj: Path, lib: Path, *, |
|
|
|
out: Path = None) -> Path: |
|
|
|
if out is None: |
|
|
|
basename = obj.stem |
|
|
|
out = HERE_DIR / '_build/test' / (basename + '.exe') |
|
|
|
out.parent.mkdir(exist_ok=True, parents=True) |
|
|
|
|
|
|
|
print(f'Linking executable {out}') |
|
|
|
cmd = _create_exe_link_command(cxx, obj, lib, out) |
|
|
|
cmd = _create_exe_link_command(opts, obj, lib, out) |
|
|
|
subprocess.check_call(cmd) |
|
|
|
return out |
|
|
|
|
|
|
@@ -179,7 +214,18 @@ 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) |
|
|
|
parser.add_argument( |
|
|
|
'--jobs', |
|
|
|
'-j', |
|
|
|
type=int, |
|
|
|
help='Set number of parallel build threads', |
|
|
|
default=multiprocessing.cpu_count() + 2) |
|
|
|
parser.add_argument( |
|
|
|
'--debug', |
|
|
|
action='store_true', |
|
|
|
help='Build with debug information and disable optimizations') |
|
|
|
parser.add_argument( |
|
|
|
'--static', action='store_true', help='Build a static executable') |
|
|
|
args = parser.parse_args(argv) |
|
|
|
|
|
|
|
all_sources = set(HERE_DIR.glob('src/**/*.cpp')) |
|
|
@@ -188,17 +234,23 @@ def main(argv: Sequence[str]) -> int: |
|
|
|
|
|
|
|
lib_sources = (all_sources - test_sources) - main_sources |
|
|
|
|
|
|
|
objects = compile_sources(Path(args.cxx), all_sources, jobs=args.jobs) |
|
|
|
build_opts = BuildOptions( |
|
|
|
cxx=Path(args.cxx), |
|
|
|
jobs=args.jobs, |
|
|
|
static=args.static, |
|
|
|
debug=args.debug) |
|
|
|
|
|
|
|
objects = compile_sources(build_opts, all_sources) |
|
|
|
|
|
|
|
lib = make_library(objects[p] for p in lib_sources) |
|
|
|
lib = make_library(build_opts, (objects[p] for p in lib_sources)) |
|
|
|
|
|
|
|
test_objs = (objects[p] for p in test_sources) |
|
|
|
pool = ThreadPoolExecutor(args.jobs) |
|
|
|
pool = ThreadPoolExecutor(build_opts.jobs) |
|
|
|
test_exes = list( |
|
|
|
pool.map(lambda o: link_exe(Path(args.cxx), o, lib), test_objs)) |
|
|
|
pool.map(lambda o: link_exe(build_opts, o, lib), test_objs)) |
|
|
|
|
|
|
|
main_exe = link_exe( |
|
|
|
Path(args.cxx), |
|
|
|
build_opts, |
|
|
|
objects[next(iter(main_sources))], |
|
|
|
lib, |
|
|
|
out=HERE_DIR / '_build/ddslim') |