carve 0.1.1

carve

Release website

Carve compile_commands.json out of Bazel build graphs for clangd-style
tooling, licensed under Apache-2.0. Carve is a clean-slate C++23
replacement for the
maintained helly25 fork of Hedron's bazel-compile-commands-extractor.
(Note that the original Hedron repository is no longer maintained.)

Test
Coverage report

Status

Working, pre-release. All three layers are implemented and tested - Layer A
(carve refresh), Layer B (bazel run //:refresh), and Layer C (the
cc_carve_aspect aspect emitting per-action shards) - along with the refresh,
aggregate, shard, and prune subcommands. Not yet published to the Bazel
Central Registry; the release tooling is in place. See
CARVE_DESIGN.md for the architecture and
docs/IMPLEMENTATION_PLAN.md for milestone status.

What it does

  1. Walks Bazel's action graph via bazel aquery.
  2. Filters to C/C++/Objective-C/CUDA compile actions.
  3. Resolves each action's header set in-process via clang's
    DependencyScanningTool.
  4. De-Bazels each compile command so clangd can introspect it without
    Bazel-specific environment.
  5. Writes an atomic compile_commands.json plus a persistent sidecar cache
    that skips re-scanning unchanged actions on re-refresh.

A working CDB does not require a full build; clangd resolves most headers
itself. Generated headers, however, must exist on disk for the scan to resolve
them - codegen-heavy targets may need a build first. Sub-second incremental
refresh on large monorepos is a Layer C (aspect) property: Layers A/B re-run
bazel aquery each time and so pay the graph-query cost regardless of edit
size. See CARVE_DESIGN.md section 3.1.

Usage

# Refresh the whole repo's compilation database (writes compile_commands.json to
# the workspace root). This is the carve_refresh rule (Layer B).
bazel run //:refresh

# Or drive the binary directly (Layer A), choosing targets/output on the CLI.
bazel run //:carve -- refresh --targets=//foo/... --output=compile_commands.json

In a consumer workspace, depend on carve in MODULE.bazel (once it is published
to the Bazel Central Registry):

bazel_dep(name = "mboworks_carve", version = "0.1.1")

# The official LLVM static archives contain LLVM bitcode, so select the
# matching compiler as well as its C++ standard library.
bazel_dep(name = "toolchains_llvm", version = "1.9.0")
llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm")
llvm.toolchain(
    name = "llvm_toolchain",
    llvm_version = "22.1.8",
    stdlib = {
        "": "builtin-libc++",
        "linux-aarch64": "stdc++",
        "linux-x86_64": "stdc++",
    },
)
use_repo(llvm, "llvm_toolchain")
register_toolchains("@llvm_toolchain//:all")

Carve links LLVM's prebuilt dependency-scanning libraries. Copy
carve.bazelrc into the consumer workspace and import it from
the workspace .bazelrc so Carve is compiled as C++23 with its required LLVM
link settings and warning policy:

try-import %workspace%/carve.bazelrc

The fragment is part of every source archive. The consumer registers the
matching toolchain because toolchains_llvm 1.9.0 only permits its toolchain
extension in the root module. Bazel's include() likewise cannot load a
fragment from an external repository, so this stanza cannot live inside Carve.
The checked-in examples/bcr module is the authoritative
consumer example used by both source-archive CI and the optional BCR
presubmit.

Then add the rule from a BUILD file:

load("@mboworks_carve//rules:carve.bzl", "carve_refresh")

carve_refresh(name = "refresh", targets = ["//..."])

carve_refresh is a bazel run target, not a build artifact: carve invokes
bazel aquery, and spawning bazel inside a build action is the nested-bazel
trap. For huge repos there is also Layer C - cc_carve_aspect +
carve_aspect_refresh (rules/cc_carve_aspect.bzl, rules/carve.bzl) schedule
one individually-cacheable shard per compile action and aggregate them.

Build requirements

License

Copyright M. Boerger, the MBO Works authors. Licensed under the Apache License 2.0; see
LICENSE for details.