| # 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()) |