import argparse
import concurrent.futures
import logging
import pathlib
import re
import subprocess
import sys
import traceback
from compiler import Compiler
from config import fix_graph
from graph import IncludeDir
from graph import run_build
from platforms import Cpu
from platforms import Os
import render
SOURCE_ROOT = pathlib.Path(__file__).parents[3].resolve()
_OS_OPTS = '|'.join(os.value for os in Os)
_CPU_OPTS = '|'.join(cpu.value for cpu in Cpu)
_PLATFORM = re.compile('^' + ''.join([
f'(?P<os>{_OS_OPTS})',
f'(?:-(?P<cpu>{_CPU_OPTS}))?',
r'(?P<xcode_suffix>_xcode\d+)?',
]) + '$')
def main(args):
logging.basicConfig(level=logging.getLevelNamesMapping()[args.verbosity])
existing_platforms = [
f.name for f in (SOURCE_ROOT / 'build/modules').iterdir()
]
filter = {
'os': args.os,
'cpu': args.cpu,
}
calls = {}
for platform in existing_platforms:
match = _PLATFORM.match(platform)
if match is None:
continue
props = match.groupdict()
os = Os(props['os'])
cpu = Cpu(props['cpu'] or 'x64')
platform = f"{os.value}-{cpu.value}"
skip = False
for k, v in filter.items():
if v is not None and props[k] != v:
skip = True
if skip:
continue
out_dir = SOURCE_ROOT / 'out' / platform
args_gn = out_dir / 'args.gn'
if not args_gn.exists():
out_dir.mkdir(exist_ok=True, parents=True)
args_gn.write_text('\n'.join([
f'target_os = "{os.value}"',
f'target_cpu = "{cpu.value}"',
'use_clang_modules = true',
'use_autogenerated_modules = true',
'use_remoteexec = true',
'symbol_level = 0',
'is_debug = false',
'running_modularize = true',
]))
error_log = None if args.error_log is None else args.error_log / platform
calls[platform] = dict(
out_dir=out_dir,
error_log=error_log,
use_cache=args.cache,
compile=args.compile,
os=os,
cpu=cpu,
)
if not calls:
print('No matching platforms. Try copying an existing one', file=sys.stderr)
elif len(calls) == 1:
_modularize(**next(iter(calls.values())))
return
with concurrent.futures.ProcessPoolExecutor() as executor:
futures = {
k: executor.submit(_modularize, **kwargs)
for k, kwargs in calls.items()
}
success = True
for platform, future in sorted(futures.items()):
exc = future.exception()
if exc is not None:
success = False
print(f'{platform} raised an exception:', file=sys.stderr)
traceback.print_exception(exc)
if not success:
exit(1)
def _modularize(out_dir: pathlib.Path, error_log: pathlib.Path | None,
use_cache: bool, compile: bool, cpu: Cpu, os: Os):
if not (out_dir / 'build.ninja').is_file():
subprocess.run(['gn', 'gen', out_dir], check=True)
compiler = Compiler(
source_root=SOURCE_ROOT,
gn_out=out_dir,
error_dir=error_log,
use_cache=use_cache,
cpu=cpu,
os=os,
)
if compile:
ps, files = compiler.compile_one(compile)
print('stderr:', ps.stderr.decode('utf-8'), file=sys.stderr)
print('Files used:')
print('\n'.join(sorted(map(str, files))))
print('Setting breakpoint to allow further debugging')
breakpoint()
return
graph = compiler.compile_all()
replacements = fix_graph(graph, compiler)
targets = run_build(graph)
platform = (out_dir / 'gen/module_platform.txt').read_text()
logging.info('Detected platform %s', platform)
out_dir = SOURCE_ROOT / 'build/modules' / platform
out_dir.mkdir(exist_ok=True, parents=False)
if compiler.sysroot_dir == IncludeDir.Sysroot:
render.render_modulemap(out_dir=out_dir,
replacements=replacements,
targets=targets)
render.render_build_gn(
out_dir=out_dir,
targets=targets,
compiler=compiler,
)
def _optional_path(s: str) -> pathlib.Path | None:
if s:
return pathlib.Path(s).resolve()
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument(
'--cpu',
help='Only update platforms matching this cpu',
type=lambda x: None if x is None else Cpu(x),
default=None,
choices=[cpu.value for cpu in Cpu],
)
parser.add_argument(
'--os',
help='Only update platforms matching this cpu',
type=lambda x: None if x is None else Os(x),
default=None,
choices=[os.value for os in Os],
)
cache = parser.add_mutually_exclusive_group(required=True)
cache.add_argument(
'--cache',
action='store_true',
help='Enable caching. Will attempt to reuse the compilation results.')
cache.add_argument(
'--no-cache',
action='store_false',
dest='cache',
help='Disable caching. Will attempt to recompile the whole libcxx, ' +
'builtins, and sysroot on every invocation')
parser.add_argument(
'--compile',
help='Compile a single header file (eg. --compile=sys/types.h) instead ' +
'of the whole sysroot. Useful for debugging.',
)
parser.add_argument('--error-log', type=_optional_path)
parser.add_argument(
'--verbosity',
help='Verbosity of logging',
default='INFO',
choices=logging.getLevelNamesMapping().keys(),
type=lambda x: x.upper(),
)
main(parser.parse_args())