Browse Source

Build on Windows with MSVC

default_compile_flags
vector-of-bool 5 years ago
parent
commit
6480c58c91
1 changed files with 108 additions and 43 deletions
  1. +108
    -43
      build.py

+ 108
- 43
build.py View File

#!/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))



Loading…
Cancel
Save