3.1.3 Repo Mapping
When you write @rules_cc//cc:defs.bzl or @maven//:org_junit_junit, the string after @ is not necessarily the directory name under $(bazel info output_base)/external/. Bazel keeps an internal canonical name for every repository and exposes apparent names that depend on which repository's BUILD file you are standing in. That per-repo table—apparent name → canonical name—is repository mapping. It is what makes strict direct-dependency visibility work under Bzlmod, why use_repo() matters for extensions, and why migration guides beg you not to hard-code paths derived from canonical names.1,2
Two names, one tree
Inside a workspace, each external repository has exactly one canonical name. A target in that repo is always reachable with a label of the form @@<canonical>//package:target (double @).1
The apparent name is the nickname a repo is allowed to use in a given context: the repo you are loading from keeps a mapping from apparent names to canonical names, so @mike//... in module alice might resolve the same tree as @mickey//... in module bob. In ordinary BUILD files you write single-@ labels. Bazel resolves them using the current repo's mapping.1
The main repository—the checkout you run Bazel in—has the empty string as its canonical name.1 Everything in 3.1.1 Bzlmod (MODULE.bazel) about “after resolution, modules become repositories” is really about Bazel synthesizing those canonical repos, then wiring apparent names so your MODULE.bazel dependencies look stable in labels.
How Bzlmod wires apparent names
For a repository that represents another module, the apparent name seen by direct dependents defaults to the dependency's module name, unless bazel_dep(..., repo_name = ...) chooses a different alias. That default is the core of strict deps: a module is not supposed to see transitive module repos just because they exist somewhere deeper in the graph—you only get names for what you declared.2
Module extensions add repos after module resolution. The extension's documentation is the contract for which repo names it creates. use_repo(extension_proxy, "maven") is what brings a generated repo into your module’s visible scope so labels like @maven//... resolve at all.3 Until that import exists, the repo may be created for another consumer but not mapped for you—this is the behavioral link back to 3.1.2 Using Extensions.
The maintainer-workspace MODULE.bazel shows that import step in context: a repo-local extension is bound, a tag is declared, and use_repo() makes the generated @release_policy repository apparent to the main module.
Canonical names are not a public path API
The canonical name format is not an API and can change. You should not bake it into strip_prefix strings, shell scripts, or deployment steps.2
Supported alternatives include:
- In Starlark, construct a
Labelfrom the apparent repo label and readLabel.repo_namewhen you need Bazel's canonical string.2 - For runfiles, prefer
$(rlocationpath …)or a language runfiles library so runtime code does not depend on a particular canonical spelling. Use the apparent repository name in the logical rlocation path, then let the library translate it for the current execution context.2,4,5 The repo-mapping-runfiles target shows the runfiles-safe shape:args = ["$(rlocationpath @aux_data//:value.txt)"]plusdata = ["@aux_data//:value.txt"], with the binary resolving the token through the standard bash runfiles library at runtime. - For tools outside Bazel (IDEs, language servers),
bazel mod dump_repo_mappingexists to dump apparent→canonical mappings for a chosen base context.2
The practical failure mode—especially after enabling Bzlmod—is packaging or scripts that matched the old external/<apparent-name>_* layout. Paths on disk live under the output base's external/ directory by canonical name, so anything that assumed the apparent string equals the directory segment silently drifts.1,6 The assumes_canonical_path.sh sibling script in the same snippet documents that anti-pattern in one place — it only prints the warning rather than actually reading the guessed path, since the canonical layout is explicitly not an API and would change under your feet.
Vendor mode exposes the same naming split in a configuration file: VENDOR.bazel directives use canonical repo names, not everyday apparent labels.
Debugging and migration
When a label @foo//... "should" exist but resolution fails, first ask which repo is the context for resolving @foo: in a BUILD file that is the repo containing the file, while labels on the Bazel command line are treated as if they come from the main repo.7 Then work outward from intent: confirm the module or extension actually provides the repo, confirm your module declares it (bazel_dep, use_repo, or use_repo_rule), then inspect how Bazel named it. bazel mod show_repo accepts both apparent (@name) and canonical (@@name) forms. Apparent names are interpreted relative to --base_module (default <root>).8,7
Brownfield moves where WORKSPACE chose arbitrary http_archive(name = …) values are exactly where naming surprises cluster: Bzlmod's canonical scheme is different, and strict visibility may hide repos you used to reach accidentally. For the broader migration track, see M1 WORKSPACE → Bzlmod. The two most relevant follow-ups here are M1.5 Repo Name Handling in Rules and M1.6 Runfiles Path Portability.4,6
Treat @repo in labels as going through a per-repo map to a canonical repository. bazel_dep and use_repo exist partly to maintain that map predictably. Do not depend on canonical strings in file paths. Use Label.repo_name, rlocationpath/runfiles libraries, or bazel mod dump_repo_mapping instead.1,2
Repository rule implementations see a subtle twist: the name passed when the rule is instantiated is the original user-facing repo name, but inside the implementation repository_ctx.name is the canonical repository name. If you need the original instantiation name, use repository_ctx.original_name. repository_ctx.attr.name also reads back the canonical value from the implicit name attribute.9,10
There are real-world cases where canonical-name churn (for example formatting changes across Bazel releases) broke assumptions embedded in scripts. Treat those as cautionary tales reinforcing the “not an API” guidance rather than as stable identifiers themselves.4
Check your understanding · 3 questions
1.A label @foo//pkg:target appears in a BUILD file inside module 'alice'. What does Bazel use to resolve 'foo' to an actual repository?
Select one answer
2.True or false about canonical repository names:
Choose True or False for each sentence
3.Match each naming concept to its description:
Drag each answer onto the matching prompt, or click an answer and then click a prompt
foo in @foo//...)Footnotes
-
External dependencies overview — canonical vs apparent names,
@@vs@, per-repo mapping, andexternal/<canonical>layout under the output base ↩1 ↩2 ↩3 ↩4 ↩5 ↩6 -
Bazel modules — strict direct-dependency visibility, default apparent names from
bazel_dep, canonical-name non-guarantees,Label.repo_name,$(rlocationpath …), andbazel mod dump_repo_mapping↩1 ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 -
Module extensions —
use_repobringing generated repos into the current module’s label scope ↩ -
Migrating to Bazel Modules - Repo Names and Runfiles — runfiles libraries,
rlocationpath, and why hard-coded canonical paths are fragile ↩1 ↩2 ↩3 -
Runfiles — runfiles paths should avoid canonical repository-name strings and use language libraries plus apparent-name
rlocationpathvalues ↩ -
Migrating to Bazel Modules - Repo Names and rules_pkg — silent packaging breaks when paths assumed apparent-on-disk names under Bzlmod ↩1 ↩2
-
Frequently asked questions — context repo, repository visibility, and command-line labels resolving from the main repo ↩1 ↩2
-
modCommand —show_repoand repo argument forms (@apparent vs@@canonical) ↩ -
Repository rules —
nameattribute apparent vsrepository_ctx.attr.namecanonical behavior ↩ -
repository_ctx —
repository_ctx.nameis canonical andrepository_ctx.original_namepreserves the instantiated name ↩