bazelbuild / bazelbuild/bazel

Feature request: `--registry-exclusive` flag for per-module registry ownership in Bzlmod

Open
#29,248 11 comments 0 reactions 0 assignees View on GitHub
awaiting-user-response P3 team-ExternalDeps type: feature request
Dominant language
Java
Stars
25.8k
Forks
4.6k
Avg merge
2d 20h
Merged PRs (30d)
72

Description

### Description of the feature request

## Problem

When using multiple `--registry` flags, Bazel merges available versions from **all** registries and MVS selects across the combined pool. There is no way to make an internal registry authoritative for specific modules.

This means an internal registry with patched or pinned module versions can be silently bypassed when BCR (or another registry) offers a higher version that a transitive dependency requests.

### Concrete example

```
# .bazelrc
common --registry=file:///path/to/internal/registry/
common --registry=https://bcr.bazel.build
```

- Internal registry has `websocketpp@0.8.2` (patched, no `boost.asio` dep)
- BCR has `websocketpp@0.8.2.bcr.5` (unpatched, depends on `boost.asio@1.89.0`)
- A transitive dependency requests `websocketpp >= 0.8.2`
- **Expected**: internal registry is listed first, so `websocketpp@0.8.2` is used
- **Actual**: MVS picks `0.8.2.bcr.5` from BCR (higher version in the merged pool), ignoring the internal registry entirely. The patch is silently lost.

This was independently confirmed by @guw in [#25722](https://github.com/bazelbuild/bazel/issues/25722):

> *"As a workaround we have an internal registry offering patched versions. But that is brittle. As soon as a newer (higher) version is available in BCR and consumed by a dependency in the graph, the patched version is lost, because even `single_version_override` is limited to the root module only."*

### Observed behavior vs documented behavior

The `--registry` flag documentation states:

> *"The order is important: modules will be looked up in earlier registries first, and only fall back to later registries when they're missing from the earlier ones."*

In practice, this is not enforced per-module. Versions from all registries are merged into a single pool before MVS runs. A module present in the first registry can still be resolved from a later registry if a higher version exists there.

### Why this matters

Organizations maintaining internal registries with patched dependencies have no reliable way to ensure their patches are used. The only workaround today is forking the entire BCR and using a single `--registry` flag, which is a significant maintenance burden.

Related: [#25722 - How to patch dependencies of non-root module in bzlmod](https://github.com/bazelbuild/bazel/issues/25722)

## Proposal: `--registry-exclusive` flag

Add a new flag `--registry-exclusive=` that marks a registry as authoritative for any module it contains.

### Behavior

When a registry is marked exclusive:

1. During module resolution, Bazel checks if the exclusive registry has a `metadata.json` for the module
2. If yes, **only versions from that registry** are considered by MVS. All other registries are skipped for that module.
3. If no, resolution proceeds normally across all registries (fallthrough)

### Usage

```
# .bazelrc
common --registry=https://internal.example.com/registry/
common --registry=https://bcr.bazel.build
common --registry-exclusive=https://internal.example.com/registry/
```

With the example above:
- `websocketpp` exists in the internal registry -> only `0.8.2` is available -> MVS picks `0.8.2` -> patch is applied
- `rules_pkg` does not exist in the internal registry -> falls through to BCR -> resolved normally

### Why this design

- **Backward compatible**: default behavior is unchanged; `--registry-exclusive` is opt-in
- **Minimal surface area**: single new flag, no changes to `MODULE.bazel` syntax or registry format
- **Composable**: multiple registries can be marked exclusive, each authoritative for its own set of modules
- **Predictable**: if a module is in an exclusive registry, it can never be silently overridden by a higher version from another registry

## Impact

This would unblock organizations that:
- Maintain internal registries with patched modules (security fixes, compatibility patches, platform-specific changes)
- Ship libraries as non-root Bazel modules that require specific dependency versions

The current workarounds (forking BCR, version bumping tricks, adding transitive deps as direct deps) are all brittle and high-maintenance.

### Which category does this issue belong to?

Core

### What underlying problem are you trying to solve with this feature?

_No response_

### Which operating system are you running Bazel on?

Linux 22.04

### What is the output of `bazel info release`?

release 7.4.0

### If `bazel info release` returns `development version` or `(@non-git)`, tell us how you built Bazel.

_No response_

### What's the output of `git remote get-url origin; git rev-parse HEAD` ?

```text

```

### Have you found anything relevant by searching the web?

_No response_

### Any other information, logs, or outputs that you want to share?

_No response_

Contributor guide

Open the contributing guide

Research direction

The issue names no source files, tests, or implementation entry points. Start by tracing Bazel's --registry handling through Bzlmod module resolution, then verify the proposed exclusive-registry behavior: modules found in an exclusive registry use only its versions, while absent modules fall through normally.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
build-system
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.