Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

215 lines
6.0KB

  1. #!/usr/bin/env python3
  2. import argparse
  3. import os
  4. from pathlib import Path
  5. import multiprocessing
  6. import itertools
  7. from concurrent.futures import ThreadPoolExecutor
  8. from typing import Sequence, Iterable, Dict, Tuple,List
  9. import subprocess
  10. import time
  11. import sys
  12. HERE_DIR = Path(__file__).parent.absolute()
  13. INCLUDE_DIRS = [
  14. 'external/taywee-args/include',
  15. 'external/spdlog/include',
  16. 'external/wil/include',
  17. ]
  18. def is_msvc(cxx: Path) -> bool:
  19. return (not 'clang' in cxx.name) and 'cl' in cxx.name
  20. def _create_compile_command(cxx: Path, cpp_file: Path, obj_file: Path) -> List[str]:
  21. if not is_msvc(cxx):
  22. cmd = [
  23. str(cxx),
  24. f'-I{HERE_DIR / "src"}',
  25. '-std=c++17',
  26. '-static',
  27. '-Wall',
  28. '-Wextra',
  29. '-Werror',
  30. '-Wshadow',
  31. '-Wconversion',
  32. '-fdiagnostics-color',
  33. '-pthread',
  34. '-g',
  35. '-c',
  36. '-O0',
  37. str(cpp_file),
  38. f'-o{obj_file}',
  39. ]
  40. cmd.extend(
  41. itertools.chain.from_iterable(
  42. ('-isystem', str(HERE_DIR / subdir)) for subdir in INCLUDE_DIRS))
  43. return cmd
  44. else:
  45. cmd = [
  46. str(cxx),
  47. # '/O2',
  48. '/Od',
  49. '/Z7',
  50. '/DEBUG',
  51. '/W4',
  52. '/WX',
  53. '/MT',
  54. '/nologo',
  55. # '/wd2220',
  56. '/EHsc',
  57. '/std:c++latest',
  58. f'/I{HERE_DIR / "src"}',
  59. str(cpp_file),
  60. '/c',
  61. f'/Fo{obj_file}',
  62. ]
  63. cmd.extend(
  64. f'/I{HERE_DIR / subdir}' for subdir in INCLUDE_DIRS)
  65. return cmd
  66. def _compile_src(cxx: Path, cpp_file: Path) -> Tuple[Path, Path]:
  67. build_dir = HERE_DIR / '_build'
  68. src_dir = HERE_DIR / 'src'
  69. relpath = cpp_file.relative_to(src_dir)
  70. obj_path = build_dir / relpath.with_name(relpath.name + ('.obj' if is_msvc(cxx) else '.o'))
  71. obj_path.parent.mkdir(exist_ok=True, parents=True)
  72. cmd = _create_compile_command(cxx, cpp_file, obj_path)
  73. msg = f'Compile C++ file: {cpp_file}'
  74. print(msg)
  75. start = time.time()
  76. res = subprocess.run(
  77. cmd,
  78. stdout=subprocess.PIPE,
  79. stderr=subprocess.STDOUT,
  80. )
  81. if res.returncode != 0:
  82. raise RuntimeError(
  83. f'Compile command ({cmd}) failed for {cpp_file}:\n{res.stdout.decode()}'
  84. )
  85. if res.stdout:
  86. print(res.stdout.decode())
  87. end = time.time()
  88. print(f'{msg} - Done: {end - start:.2}s')
  89. return cpp_file, obj_path
  90. def compile_sources(cxx: Path, sources: Iterable[Path], *, jobs: int) -> Dict[Path, Path]:
  91. pool = ThreadPoolExecutor(jobs)
  92. return {
  93. src: obj
  94. for src, obj in pool.map(lambda s: _compile_src(cxx, s), sources)
  95. }
  96. def _create_archive_command(objects: Iterable[Path]) -> List[str]:
  97. if os.name == 'nt':
  98. lib_file = HERE_DIR / '_build/libddslim.lib'
  99. cmd = ['lib', '/nologo', f'/OUT:{lib_file}', *map(str, objects)]
  100. return lib_file, cmd
  101. else:
  102. lib_file = HERE_DIR / '_build/libddslim.a'
  103. cmd = ['ar', 'rsc', str(lib_file), *objects]
  104. return lib_file, cmd
  105. def make_library(objects: Iterable[Path]) -> Path:
  106. lib_file, cmd = _create_archive_command(objects)
  107. if lib_file.exists():
  108. lib_file.unlink()
  109. print(f'Creating static library {lib_file}')
  110. subprocess.check_call(cmd)
  111. return lib_file
  112. def _create_exe_link_command(cxx: Path, obj: Path, lib: Path, out: Path) -> List[str]:
  113. if not is_msvc(cxx):
  114. return [
  115. str(cxx),
  116. '-static',
  117. '-pthread',
  118. # See: https://stackoverflow.com/questions/35116327/when-g-static-link-pthread-cause-segmentation-fault-why
  119. '-Wl,--whole-archive',
  120. '-lpthread',
  121. '-Wl,--no-whole-archive',
  122. str(obj),
  123. str(lib),
  124. '-lstdc++fs',
  125. f'-o{out}',
  126. ]
  127. else:
  128. return [
  129. str(cxx),
  130. '/nologo',
  131. '/W4',
  132. '/WX',
  133. '/MT',
  134. '/Z7',
  135. '/DEBUG',
  136. f'/Fe{out}',
  137. str(lib),
  138. str(obj),
  139. ]
  140. def link_exe(cxx: Path, obj: Path, lib: Path, *, out: Path = None) -> Path:
  141. if out is None:
  142. basename = obj.stem
  143. out = HERE_DIR / '_build/test' / (basename + '.exe')
  144. out.parent.mkdir(exist_ok=True, parents=True)
  145. print(f'Linking executable {out}')
  146. cmd = _create_exe_link_command(cxx, obj, lib, out)
  147. subprocess.check_call(cmd)
  148. return out
  149. def run_test(exe: Path) -> None:
  150. print(f'Running test: {exe}')
  151. subprocess.check_call([exe])
  152. def main(argv: Sequence[str]) -> int:
  153. parser = argparse.ArgumentParser()
  154. parser.add_argument(
  155. '--test', action='store_true', help='Build and run tests')
  156. parser.add_argument(
  157. '--cxx', help='Path/name of the C++ compiler to use.', required=True)
  158. parser.add_argument('--jobs', '-j', type=int, help='Set number of parallel build threads', default=multiprocessing.cpu_count() + 2)
  159. args = parser.parse_args(argv)
  160. all_sources = set(HERE_DIR.glob('src/**/*.cpp'))
  161. test_sources = set(HERE_DIR.glob('src/**/*.test.cpp'))
  162. main_sources = set(HERE_DIR.glob('src/**/*.main.cpp'))
  163. lib_sources = (all_sources - test_sources) - main_sources
  164. objects = compile_sources(Path(args.cxx), all_sources, jobs=args.jobs)
  165. lib = make_library(objects[p] for p in lib_sources)
  166. test_objs = (objects[p] for p in test_sources)
  167. pool = ThreadPoolExecutor(args.jobs)
  168. test_exes = list(
  169. pool.map(lambda o: link_exe(Path(args.cxx), o, lib), test_objs))
  170. main_exe = link_exe(
  171. Path(args.cxx),
  172. objects[next(iter(main_sources))],
  173. lib,
  174. out=HERE_DIR / '_build/ddslim')
  175. if args.test:
  176. list(pool.map(run_test, test_exes))
  177. print(f'Main executable generated at {main_exe}')
  178. return 0
  179. if __name__ == "__main__":
  180. sys.exit(main(sys.argv[1:]))