From 2ee4b8472f3ce19c69b09c5781f1b76123e1dfea Mon Sep 17 00:00:00 2001 From: Santiago Saffon Date: Thu, 17 Sep 2026 11:57:19 -0500 Subject: [PATCH] Recognize ObjcCompile as a compile action in the Bazel extractor Why: extract_bazel.py decides which bazel aquery actions count as "a compile" with a set _COMPILE, ObjcCompile wasn't in that set, extract_bazel.py silently dropped every Objective-C compile action when building model.bazel.json. On CMake's side, the File API reports .m The result: the differ saw sources that CMake compiled and Bazel appeared not to, and reported them as missing_tu errors a false signal. The files were compiling fine (bazel build succeeds); the extractor just couldn't see the actions. Verification: - Full test suite passes (127 tests, up from 126). - Re-ran against a real captured raylib migration (//:glfw, //:glfw_cocoa, //:raylib): regenerating aquery.json with ObjcCompile added to the mnemonic filter and re-running the extractor/differ eliminates all 5 missing_tu false positives. The 5 .m files now correctly participate in the diff --- SKILL.md | 2 +- scripts/canonicalize.py | 3 ++- scripts/extract_bazel.py | 16 ++++++++++------ scripts/reconstruct.py | 4 ++-- tests/test_extractors.py | 28 ++++++++++++++++++++++++++++ 5 files changed, 43 insertions(+), 10 deletions(-) diff --git a/SKILL.md b/SKILL.md index 00621eb..1b5af20 100644 --- a/SKILL.md +++ b/SKILL.md @@ -255,7 +255,7 @@ instead of being a warning nobody reads. > two sides aren't comparable. ```bash -bazel aquery 'mnemonic("CppCompile|CppLink|CppArchive", //...)' \ +bazel aquery 'mnemonic("CppCompile|ObjcCompile|CppLink|CppArchive", //...)' \ [--config=] [--copt=... --cxxopt=...] \ --output=jsonproto > aquery.json python3 scripts/extract_bazel.py aquery.json model.bazel.json diff --git a/scripts/canonicalize.py b/scripts/canonicalize.py index 4742750..f2b8c66 100644 --- a/scripts/canonicalize.py +++ b/scripts/canonicalize.py @@ -151,7 +151,8 @@ def _is_driver_token(tok: str) -> bool: these, so this only affects the Bazel side.""" if tok.startswith("-"): return False - return (tok.endswith((".sh", ".o", ".obj", ".cc", ".cpp", ".cxx", ".c", ".C")) + return (tok.endswith((".sh", ".o", ".obj", ".cc", ".cpp", ".cxx", ".c", ".C", + ".m", ".mm")) or "/" in tok and not tok.startswith("/")) # relative exec/source paths diff --git a/scripts/extract_bazel.py b/scripts/extract_bazel.py index 6ea40fb..f87b4f0 100644 --- a/scripts/extract_bazel.py +++ b/scripts/extract_bazel.py @@ -15,12 +15,16 @@ """Extract a CanonicalModel from `bazel aquery --output=jsonproto`. aquery is the Bazel-side counterpart to the CMake File API: it exposes the -actual actions the build would run -- CppCompile actions (one per TU, with the -full argv) AND CppLink/CppArchive actions (the link closure). compile commands -alone would miss the link half, so we use aquery. +actual actions the build would run -- CppCompile/ObjcCompile actions (one per +TU, with the full argv) AND CppLink/CppArchive actions (the link closure). +compile commands alone would miss the link half, so we use aquery. ObjcCompile +is Bazel's mnemonic for .m/.mm sources compiled via objc_library -- CMake's +File API has no such distinction (it reports those under CppCompile too), so +both must map to the same neutral compile bucket here or objc_library sources +silently vanish from the Bazel model. Usage: - bazel aquery 'mnemonic("CppCompile|CppLink|CppArchive", //...)' \ + bazel aquery 'mnemonic("CppCompile|ObjcCompile|CppLink|CppArchive", //...)' \ --output=jsonproto > aquery.json python3 extract_bazel.py aquery.json model.bazel.json @@ -44,7 +48,7 @@ TargetRole) from serialize import dump_model -_COMPILE = {"CppCompile"} +_COMPILE = {"CppCompile", "ObjcCompile"} _LINK = {"CppLink", "CppArchive"} # Bazel Java compile. (Turbine = header/ijar compile, JavaSourceJar = packaging: # both Bazel-specific, not real compilations -- skipped, like C++ header @@ -108,7 +112,7 @@ def _label_to_name(label: str) -> str: _HEADER_PROCESSING_MARKERS = ("-xc++-header", "-fsyntax-only") -_REAL_SOURCE_EXTS = (".cc", ".cpp", ".cxx", ".c", ".C") +_REAL_SOURCE_EXTS = (".cc", ".cpp", ".cxx", ".c", ".C", ".m", ".mm") def _is_real_compile(args) -> bool: diff --git a/scripts/reconstruct.py b/scripts/reconstruct.py index f3f3a35..8e9a606 100644 --- a/scripts/reconstruct.py +++ b/scripts/reconstruct.py @@ -40,12 +40,12 @@ from model import (Action, BuildSystem, CanonicalModel, Dependency, Target, TargetKind, TranslationUnit) -_COMPILE_MNEMONICS = {"CppCompile"} +_COMPILE_MNEMONICS = {"CppCompile", "ObjcCompile"} _LINK_MNEMONICS = {"CppLink", "CppArchive"} _JAVA_COMPILE_MNEMONICS = {"JavaCompile"} _HEADER_EXTS = (".h", ".hpp", ".hh", ".hxx", ".inc", ".inl") -_SOURCE_EXTS = (".cc", ".cpp", ".cxx", ".c", ".C") +_SOURCE_EXTS = (".cc", ".cpp", ".cxx", ".c", ".C", ".m", ".mm") _ARCHIVE_EXTS = (".a", ".lo", ".lib") _JAVA_EXT = ".java" diff --git a/tests/test_extractors.py b/tests/test_extractors.py index daf935b..fd282b7 100644 --- a/tests/test_extractors.py +++ b/tests/test_extractors.py @@ -191,6 +191,34 @@ def test_header_processing_actions_are_not_tus(): assert not any(s.endswith(".h") for s in srcs), srcs # header dropped +def test_bazel_objccompile_is_a_real_compile(): + # objc_library sources (.m/.mm) show up under Bazel's ObjcCompile mnemonic, + # not CppCompile. CMake's File API has no such split -- it reports them as + # CppCompile too -- so ObjcCompile must land in the same TU bucket, or an + # Objective-C target's sources silently vanish from the Bazel model (they + # still compile fine; only the extractor's view of them was missing). + aquery = { + "artifacts": [{"id": 1, "pathFragmentId": 10}], + "pathFragments": [{"id": 10, "label": "cocoa_init.o", "parentId": 11}, + {"id": 11, "label": "bazel-out"}], + "targets": [{"id": 100, "label": "//:glfw_cocoa"}], + "actions": [ + {"mnemonic": "ObjcCompile", "targetId": 100, "outputIds": [1], + "arguments": ["clang", "-c", "src/cocoa_init.m", + "-o", "bazel-out/cocoa_init.o", "-DFOO=1"]}, + ], + } + with tempfile.TemporaryDirectory() as root: + aq_path = os.path.join(root, "aquery.json") + with open(aq_path, "w") as f: + json.dump(aquery, f) + b = extract_bazel.extract(aq_path, REPO) + t = _view(b, ":glfw_cocoa") + srcs = [tu.source for tu in t.tus] + assert "src/cocoa_init.m" in srcs, srcs + assert t.role.value == "production", t.role + + def test_bazel_extracts_link_flags_from_cpplink(): # A CppLink action: link flags must be extracted; driver mechanics (wrapper, # -o/output), object/archive inputs and -l libs must be dropped.