#!/usr/bin/env python3 import glob import os import subprocess import sys from dataclasses import dataclass from dataclasses import field from typing import Any from typing import Dict from typing import List STD_MODULE = """module std [system] { textual header "/usr/include/alloca.h" textual header "/usr/include/assert.h" textual header "/usr/include/c++/14.2.0/algorithm" textual header "/usr/include/c++/14.2.0/array" textual header "/usr/include/c++/14.2.0/atomic" textual header "/usr/include/c++/14.2.0/cassert" textual header "/usr/include/c++/14.2.0/cerrno" textual header "/usr/include/c++/14.2.0/chrono" textual header "/usr/include/c++/14.2.0/climits" textual header "/usr/include/c++/14.2.0/compare" textual header "/usr/include/c++/14.2.0/cstddef" textual header "/usr/include/c++/14.2.0/cstdint" textual header "/usr/include/c++/14.2.0/cstdio" textual header "/usr/include/c++/14.2.0/cstdlib" textual header "/usr/include/c++/14.2.0/cstring" textual header "/usr/include/c++/14.2.0/deque" textual header "/usr/include/c++/14.2.0/functional" textual header "/usr/include/c++/14.2.0/iomanip" textual header "/usr/include/c++/14.2.0/iosfwd" textual header "/usr/include/c++/14.2.0/iostream" textual header "/usr/include/c++/14.2.0/limits" textual header "/usr/include/c++/14.2.0/map" textual header "/usr/include/c++/14.2.0/memory" textual header "/usr/include/c++/14.2.0/mutex" textual header "/usr/include/c++/14.2.0/new" textual header "/usr/include/c++/14.2.0/optional" textual header "/usr/include/c++/14.2.0/ostream" textual header "/usr/include/c++/14.2.0/queue" textual header "/usr/include/c++/14.2.0/random" textual header "/usr/include/c++/14.2.0/stdlib.h" textual header "/usr/include/c++/14.2.0/string" textual header "/usr/include/c++/14.2.0/thread" textual header "/usr/include/c++/14.2.0/type_traits" textual header "/usr/include/c++/14.2.0/vector" textual header "/usr/include/errno.h" textual header "/usr/include/fortify/stdio.h" textual header "/usr/include/fortify/string.h" textual header "/usr/include/fortify/unistd.h" textual header "/usr/include/limits.h" textual header "/usr/include/stdarg.h" textual header "/usr/include/stdbool.h" textual header "/usr/include/stddef.h" textual header "/usr/include/stdint.h" textual header "/usr/include/sys/time.h" textual header "/usr/include/sys/types.h" textual header "/usr/include/time.h" } module "c_toxcore_third_party_cmp" { header "third_party/cmp/cmp.h" use std } module "c_toxcore_toxencryptsave_defines" { header "toxencryptsave/defines.h" } module "_benchmark" { textual header "/usr/include/benchmark/benchmark.h" use std } module "_com_google_googletest___gtest" { textual header "/usr/include/gmock/gmock.h" textual header "/usr/include/gtest/gtest.h" use std } module "_com_google_googletest___gtest_main" { // Dummy module for gtest_main, assuming it links but headers are in gtest use "_com_google_googletest___gtest" } module "_libsodium" { textual header "/usr/include/sodium.h" } module "_pthread" { textual header "/usr/include/pthread.h" } module "_psocket" { textual header "/usr/include/arpa/inet.h" textual header "/usr/include/fcntl.h" textual header "/usr/include/fortify/sys/socket.h" textual header "/usr/include/linux/if.h" textual header "/usr/include/netdb.h" textual header "/usr/include/netinet/in.h" textual header "/usr/include/sys/epoll.h" textual header "/usr/include/sys/ioctl.h" } """ @dataclass class Target: name: str package: str srcs: List[str] = field(default_factory=list) hdrs: List[str] = field(default_factory=list) deps: List[str] = field(default_factory=list) @property def label(self) -> str: # Sanitize label for module name sanitized = (f"//c-toxcore/{self.package}:{self.name}".replace( "/", "_").replace(":", "_").replace(".", "_").replace("-", "_").replace("@", "_")) if sanitized.startswith("__"): sanitized = sanitized[2:] return sanitized TARGETS: List[Target] = [] class BuildContext: def __init__(self, package: str): self.package = package def bzl_load(self, *args: Any, **kwargs: Any) -> None: pass def bzl_exports_files(self, *args: Any, **kwargs: Any) -> None: pass def bzl_alias(self, *args: Any, **kwargs: Any) -> None: pass def bzl_sh_library(self, *args: Any, **kwargs: Any) -> None: pass def bzl_cc_fuzz_test(self, *args: Any, **kwargs: Any) -> None: pass def bzl_select(self, selector: Dict[str, List[str]]) -> List[str]: return selector.get("//tools/config:linux", selector.get("//conditions:default", [])) def bzl_glob(self, include: List[str]) -> List[str]: results = [] for pattern in include: full_pattern = os.path.join(self.package, pattern) files = glob.glob(full_pattern) results.extend([os.path.relpath(f, self.package) for f in files]) return results def _add_target(self, name: str, srcs: Any, hdrs: Any, deps: Any) -> None: srcs = list(srcs) if srcs else [] hdrs = list(hdrs) if hdrs else [] deps = list(deps) if deps else [] TARGETS.append(Target(name, self.package, srcs, hdrs, deps)) def bzl_cc_library(self, name: str, srcs: Any = (), hdrs: Any = (), deps: Any = (), **kwargs: Any) -> None: self._add_target(name, srcs, hdrs, deps) def bzl_cc_binary(self, name: str, srcs: Any = (), hdrs: Any = (), deps: Any = (), **kwargs: Any) -> None: self._add_target(name, srcs, hdrs, deps) def bzl_cc_test(self, name: str, srcs: Any = (), hdrs: Any = (), deps: Any = (), **kwargs: Any) -> None: self._add_target(name, srcs, hdrs, deps) def resolve_module_name(dep: str, current_pkg: str) -> str: # Resolve to canonical label first label = dep if dep.startswith("@"): label = dep elif dep.startswith("//"): if ":" in dep: label = dep else: pkg_name = os.path.basename(dep) label = f"{dep}:{pkg_name}" elif dep.startswith(":"): label = f"//c-toxcore/{current_pkg}{dep}" # Sanitize sanitized = (label.replace("/", "_").replace(":", "_").replace( ".", "_").replace("-", "_").replace("@", "_")) if sanitized.startswith("__"): sanitized = sanitized[2:] return sanitized def main() -> None: packages = ["toxcore", "testing/support"] for pkg in packages: ctx = BuildContext(pkg) build_file = os.path.join(pkg, "BUILD.bazel") if not os.path.exists(build_file): continue with open(build_file, "r") as f: exec( f.read(), { "load": ctx.bzl_load, "exports_files": ctx.bzl_exports_files, "cc_library": ctx.bzl_cc_library, "cc_binary": ctx.bzl_cc_binary, "cc_test": ctx.bzl_cc_test, "cc_fuzz_test": ctx.bzl_cc_fuzz_test, "select": ctx.bzl_select, "glob": ctx.bzl_glob, "alias": ctx.bzl_alias, "sh_library": ctx.bzl_sh_library, }, ) with open("module.modulemap", "w") as f: f.write(STD_MODULE) for t in TARGETS: f.write(f'module "{t.label}" {{\n') for hdr in t.hdrs: f.write(f' header "{os.path.join(t.package, hdr)}"\n') f.write(" use std\n") for dep in t.deps: mod_name = resolve_module_name(dep, t.package) # Export all dependencies to ensure visibility of types used in headers f.write(f" use {mod_name}\n") f.write(f" export {mod_name}\n") f.write("}\n") # with open("module.modulemap", "r") as f: # print(f.read(), file=sys.stderr) src_to_module = {} for t in TARGETS: for src in t.srcs: full_src = os.path.join(t.package, src) src_to_module[full_src] = t.label # Sort for deterministic output all_srcs = sorted(src_to_module.keys()) for src in all_srcs: print(f"Validating {src}", file=sys.stderr) module_name = src_to_module[src] subprocess.run( [ "clang", "-fsyntax-only", "-xc++", "-Wall", "-Werror", "-Wno-missing-braces", "-DTCP_SERVER_USE_EPOLL", "-std=c++23", "-fdiagnostics-color=always", "-fmodules", "-fmodules-strict-decluse", "-fmodule-map-file=module.modulemap", f"-fmodule-name={module_name}", "-I.", src, ], check=True, ) if __name__ == "__main__": main()