bzlmod examples
Copy-pasteable MODULE.bazel recipes for the tools extension. For the full
API reference see bzlmod hub-and-spoke; to convert an existing
WORKSPACE project see Migrating from WORKSPACE.
You usually don't call
register_toolchainsyourself.rules_foreign_cc's ownMODULE.bazelalready registers@rules_foreign_cc_toolchains//:all, and bzlmod honors a dependency's registration for the whole build. Yourtools.<tool>(...)tags shape what the hub contains; you don't need to re-register it oruse_repoit. The only time you callregister_toolchains/use_repoyourself is the Bring your own toolchain case below, or when you reference a hub alias / spoke repo by name in your own BUILD files.
Defaults only
Accept every tool's default mode and pinned version - just depend on
rules_foreign_cc:
bazel_dep(name = "rules_foreign_cc", version = "{version}")
Pin a single version
Override just cmake; every other tool keeps its default:
bazel_dep(name = "rules_foreign_cc", version = "{version}")
tools = use_extension("@rules_foreign_cc//foreign_cc:extensions.bzl", "tools")
tools.cmake(version = "3.31.12", mode = "binary")
Note: overriding a tool replaces all of rfcc's default tags for that tool,
not just the one you restate. cmake and ninja ship two defaults each - the
prebuilt binary plus an unconstrained system host-PATH fallback (see
Minimal setup) - so a lone
tools.cmake(mode = "binary") drops the cmake system fallback, and resolution
will fail on platforms rfcc has no prebuilt for. To keep the fallback, restate
both tags:
tools.cmake(version = "3.31.12", mode = "binary")
tools.cmake(mode = "system")
Track the latest patch of a minor series
Use a major.minor.x wildcard to take whatever patch rules_foreign_cc
ships for that minor series (here 3.31.x resolves to 3.31.12):
tools = use_extension("@rules_foreign_cc//foreign_cc:extensions.bzl", "tools")
tools.cmake(version = "3.31.x", mode = "binary")
Build a tool from source
Force cmake to build from source instead of downloading a prebuilt binary:
tools = use_extension("@rules_foreign_cc//foreign_cc:extensions.bzl", "tools")
tools.cmake(version = "3.31.12", mode = "source")
Use a host-installed tool
Skip downloading/building and use the tool already on the exec host. This is not hermetic - the build depends on what's installed - but it's useful for tools you don't want rules_foreign_cc to manage:
tools = use_extension("@rules_foreign_cc//foreign_cc:extensions.bzl", "tools")
tools.make(mode = "system")
tools.ninja(mode = "system")
Full control with tools.explicit()
Suppress rfcc's default registrations entirely and register only what you declare. Any tool you don't tag has no registered toolchain, so resolution fails for it:
tools = use_extension("@rules_foreign_cc//foreign_cc:extensions.bzl", "tools")
tools.explicit()
tools.cmake(version = "3.31.12", mode = "binary")
tools.ninja(version = "1.13.2", mode = "binary")
tools.make(version = "4.4.1", mode = "source")
Bring your own toolchain
Declare the spoke but register it yourself (e.g. with custom constraints or
conditional logic). register_toolchain = False suppresses the hub entry:
# MODULE.bazel
tools = use_extension("@rules_foreign_cc//foreign_cc:extensions.bzl", "tools")
tools.cmake(version = "3.31.12", mode = "binary", register_toolchain = False)
use_repo(tools, "cmake-3.31.12-linux-x86_64")
register_toolchains("//:cmake_tool_manual")
# BUILD.bazel
toolchain(
name = "cmake_tool_manual",
exec_compatible_with = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
toolchain = "@cmake-3.31.12-linux-x86_64//:cmake_tool",
toolchain_type = "@rules_foreign_cc//toolchains:cmake_toolchain",
)
No-op a tool you don't need to run
Some build systems require a toolchain to resolve even if the tool is never
run, or treat a tool as optional and simply skip the work when it's absent.
mode = "noop" registers a placeholder that satisfies resolution
(host-independent, unlike mode = "system") and points the tool at a path
that fails when executed.
Whether that matters depends on the build: if nothing ever invokes the tool -
e.g. a cmake build that declares an m4 dependency it doesn't use, or
pkgconfig set to noop to skip optional package searches - the build succeeds
and just skips that step. If the build does invoke the tool, it fails loudly
at execution time rather than silently using a host binary.
tools = use_extension("@rules_foreign_cc//foreign_cc:extensions.bzl", "tools")
tools.explicit()
tools.cmake(version = "3.31.12", mode = "binary")
tools.ninja(version = "1.13.2", mode = "binary")
tools.m4(mode = "noop")
tools.pkgconfig(mode = "noop")