blob: 9419082003245ed13d98d92cc0875aa61fe014aa [file] [log] [blame]
# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import collections
import contextlib
import io
import pathlib
from compiler import Compiler
import config
from graph import IncludeDir
from graph import Target
_HEADER = """# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# AUTOGENERATED FILE. DO NOT EDIT.
# To regenerate, see instructions at build/modules/modularize/README.md
"""
_RULE_MAP = {
IncludeDir.Builtin: 'builtin_module',
IncludeDir.Framework: 'framework_module',
IncludeDir.LibCxx: 'libcxx_module',
IncludeDir.Sysroot: 'sysroot_module',
IncludeDir.SysrootModule: 'apple_sysroot_module',
}
_SYSROOT_MODULEMAP = """sysroot_modulemap("sysroot_modulemap") {
source = "module.modulemap.in"
out = "${target_gen_dir}/module.modulemap"
}
"""
def _filter_targets(targets: list[Target], compiler: Compiler) -> list[Target]:
module_to_modulemap = compiler.modulemaps_for_modules()
modulemap_to_modules = collections.defaultdict(list)
for k, v in module_to_modulemap.items():
modulemap_to_modules[v].append(k)
remaining = [target for target in targets if config.should_compile(target)]
compiled = set(remaining)
targets = {target.name: target for target in targets}
while remaining:
target = remaining.pop()
for dep in target.public_deps:
dep = targets[dep]
if dep not in compiled:
compiled.add(dep)
remaining.append(dep)
# If we use -fmodule-map-file=c_standard_library.modulemap for one target,
# all modules in c_standard_library.modulemap need to be precompiled.
if dep.name not in module_to_modulemap:
continue
for module in modulemap_to_modules[module_to_modulemap[dep.name]]:
if module in targets and targets[module] not in compiled:
compiled.add(targets[module])
remaining.append(targets[module])
return sorted(compiled)
def _update_content(path: pathlib.Path, content: str):
"""Updates the content of a file if changed, thus preventing a siso reload."""
with contextlib.suppress(FileNotFoundError):
if path.read_text() == content:
return
path.write_text(content)
def render_modulemap(out_dir: pathlib.Path, replacements: dict[pathlib.Path,
str],
targets: list[Target]):
"""Writes a modulemap to {out_dir}"""
f = io.StringIO()
f.write(_HEADER.replace('#', '//'))
for target in targets:
if target.include_dir != IncludeDir.Sysroot:
continue
f.write(f'\nmodule {target.name} [system] [extern_c] {{\n')
for header in target.headers:
assert header.include_dir == IncludeDir.Sysroot
f.write(f' module {header.submodule_name} {{\n')
for single in sorted(header.group):
assert single.abs is not None
path = str(single.abs)
for frm, to in replacements.items():
path = path.replace(str(frm), to)
# After replacements, all paths should be absolute.
assert not path.startswith('/'), path
f.write(f' header "{path}"\n')
if not header.exports and header.exports is not None:
f.write(' export *\n')
for export in header.exports or []:
f.write(f' export {export.submodule_name}\n')
f.write(' }\n')
f.write('}\n')
_update_content(out_dir / 'module.modulemap.in', f.getvalue())
def _render_string_list(f, indent: int, key: str, values: list[str]):
if not values:
return
indent = " " * indent
f.write(f'{indent}{key} = ')
if len(values) == 1:
f.write(f'[ "{values[0]}" ]\n')
elif len(values) > 1:
f.write(f'[\n')
for value in values:
f.write(f'{indent} "{value}",\n')
f.write(f'{indent}]\n')
def render_build_gn(out_dir: pathlib.Path, targets: list[Target],
compiler: Compiler):
"""Renders a BUILD.gn file for a specific platform to {out_dir}"""
targets = _filter_targets(targets, compiler)
f = io.StringIO()
f.write(_HEADER)
f.write('import("//buildtools/third_party/libc++/modules.gni")\n\n')
all_modulemap_configs = set()
if compiler.sysroot_dir == IncludeDir.Sysroot:
all_modulemap_configs.add("sysroot_modulemap")
f.write(_SYSROOT_MODULEMAP)
module_to_modulemaps = compiler.modulemaps_for_modules()
for target in sorted(targets):
rule = _RULE_MAP[target.include_dir]
modulemap_target = None
if target.name in module_to_modulemaps:
kind, rel = compiler.split_path(module_to_modulemaps[target.name])
modulemap_target = rel.removesuffix('/module.modulemap').removesuffix(
'.modulemap').replace('/', '_') + '_modulemap'
if kind == IncludeDir.Framework and modulemap_target not in all_modulemap_configs:
f.write(f'framework_modulemap("{modulemap_target}") {{\n')
f.write(f' name = "{modulemap_target.removesuffix("_modulemap")}"\n')
f.write('}\n')
all_modulemap_configs.add(modulemap_target)
elif kind == IncludeDir.SysrootModule and modulemap_target not in all_modulemap_configs:
f.write(f'apple_sysroot_modulemap("{modulemap_target}") {{\n')
f.write(f' sysroot_path = "{rel}"\n')
f.write('}\n')
all_modulemap_configs.add(modulemap_target)
f.write(f'{rule}("{target.name}") {{\n')
if target.include_dir in [IncludeDir.SysrootModule, IncludeDir.Framework
] and modulemap_target is not None:
f.write(f' modulemap = ":{modulemap_target}"\n')
if kind == IncludeDir.SysrootModule:
f.write(f' modulemap_path = "{rel}"\n')
_render_string_list(f, 2, 'public_deps',
[f':{dep}' for dep in target.public_deps])
for k, v in sorted(target.kwargs.items()):
_render_string_list(f, 2, k, sorted(v))
f.write('}\n\n')
f.write('group("all_modules") {\n')
f.write(' public_deps = [\n')
for target in sorted(targets):
f.write(f' ":{target.name}",\n')
f.write(' ]\n')
f.write('}\n\n')
f.write('config("all_modulemap_configs") {\n')
f.write(' configs = [\n')
f.write(' "//buildtools/third_party/libc++:builtin_modulemap",\n')
f.write(' "//buildtools/third_party/libc++:libcxx_modulemap",\n')
for target in sorted(all_modulemap_configs):
f.write(f' ":{target}",\n')
f.write(' ]\n')
f.write('}\n')
_update_content(out_dir / 'BUILD.gn', f.getvalue())