Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/REFERENCE-cmake.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ aquery must mirror.

- **Target names** in `target_map` and `exclude_targets` keys are CMake target
names (e.g. `"bssl": ":bssl"`).
- **Project in a subdirectory**: `cmake -S` may be any directory under
`<repo_root>` (e.g. one CMake project per firmware image inside a larger
Bazel workspace). The File API reports in-tree sources relative to that
top-level source dir; the extractor resolves them against the codemodel's
`paths.source`, so TU keys stay relative to `<repo_root>`.
- **`dep_map`**: CMake spells external link deps as an archive basename
(`Catch2Main`) or an imported target (`OpenSSL::SSL`), while Bazel spells them
`catch2_main` / `ssl`.
Expand Down
15 changes: 11 additions & 4 deletions scripts/extract_cmake.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
python3 extract_cmake.py <build> <repo_root> model.cmake.json

The reply lands in <build>/.cmake/api/v1/reply/. We read the codemodel index,
then each per-target jsonFile.
then each per-target jsonFile. <src> may be <repo_root> or any directory under
it: source paths are resolved against the codemodel's top-level source dir.
"""

from __future__ import annotations
Expand Down Expand Up @@ -139,10 +140,14 @@ def _target_id_to_name(codemodel: dict) -> Dict[str, str]:
return out


def _parse_target(tobj: dict, repo_root: str) -> Target:
def _parse_target(tobj: dict, source_dir: str) -> Target:
name = tobj["name"]
kind = _KIND.get(tobj.get("type", ""), TargetKind.UNKNOWN)
sources = [s["path"] for s in tobj.get("sources", [])]
# The File API gives sources inside the top-level source dir relative to IT
# (not to repo_root, which differs when `cmake -S` is a subdirectory), and
# others absolute. Make them all absolute so they key repo-relative later.
sources = [os.path.normpath(os.path.join(source_dir, s["path"]))
for s in tobj.get("sources", [])]

# Synthesize one CppCompile Action per source: an argv the differ parses the
# same way it parses Bazel's. CMake has no real command line, so we build the
Expand Down Expand Up @@ -230,14 +235,16 @@ def extract(build_dir: str, repo_root: str,
reply_dir = os.path.join(build_dir, ".cmake", "api", "v1", "reply")
codemodel = _find_codemodel(reply_dir)
id_to_name = _target_id_to_name(codemodel)
# top-level source dir (`cmake -S`); may be any directory under repo_root
source_dir = codemodel.get("paths", {}).get("source") or repo_root

model = CanonicalModel(build_system=BuildSystem.CMAKE, repo_root=repo_root)
include_dirs = set()
cfg = codemodel["configurations"][0]
for tref in cfg["targets"]:
with open(os.path.join(reply_dir, tref["jsonFile"])) as f:
tobj = json.load(f)
target = _parse_target(tobj, repo_root)
target = _parse_target(tobj, source_dir)
_attach_deps(target, tobj, id_to_name)
model.add(target)
for cg in tobj.get("compileGroups", []):
Expand Down
26 changes: 23 additions & 3 deletions tests/test_extractors.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,15 @@ def _view(model, name):
}


def _write_cmake_fixture(root):
def _write_cmake_fixture(root, codemodel=CODEMODEL, target=TARGET_MYLIB):
reply = os.path.join(root, "build", ".cmake", "api", "v1", "reply")
os.makedirs(reply)
with open(os.path.join(reply, "index-0.json"), "w") as f:
json.dump({"objects": [{"kind": "codemodel", "jsonFile": "codemodel.json"}]}, f)
with open(os.path.join(reply, "codemodel.json"), "w") as f:
json.dump(CODEMODEL, f)
json.dump(codemodel, f)
with open(os.path.join(reply, "target-mylib.json"), "w") as f:
json.dump(TARGET_MYLIB, f)
json.dump(target, f)
return os.path.join(root, "build")


Expand Down Expand Up @@ -150,6 +150,26 @@ def test_cmake_extracts_canonical_flags():
assert "-std=c++17" in tu.flags


def test_cmake_sources_resolve_against_top_level_source_dir():
# `cmake -S sub/proj` with repo_root one level up: the File API gives
# in-tree sources relative to the TOP-LEVEL SOURCE DIR (paths.source), not
# to repo_root, and out-of-tree sources absolute. TU keys must still be
# repo-relative, else every source looks missing/extra against Bazel.
codemodel = dict(CODEMODEL, paths={"source": REPO + "/sub/proj",
"build": REPO + "/sub/proj/build"})
target = dict(TARGET_MYLIB,
sources=[{"path": "src/a.cpp"}, {"path": "/opt/sdk/s.c"}],
compileGroups=[dict(TARGET_MYLIB["compileGroups"][0],
sourceIndexes=[0, 1])])
with tempfile.TemporaryDirectory() as root:
build = _write_cmake_fixture(root, codemodel, target)
a = extract_cmake.extract(build, REPO)
tus = _view(a, "mylib").tu_map()
assert sorted(tus) == ["/opt/sdk/s.c", "sub/proj/src/a.cpp"], sorted(tus)
compile_inputs = [act.inputs for act in a.targets["mylib"].actions]
assert (REPO + "/sub/proj/src/a.cpp",) in compile_inputs, compile_inputs


def test_bazel_infers_kind_and_drops_defaults():
with tempfile.TemporaryDirectory() as root:
aq_path = os.path.join(root, "aquery.json")
Expand Down