Skip to content

Commit 067ce4d

Browse files
fmeumWyverald
andauthored
[7.4.0] Add override_repo and inject_repo (#23938)
Work towards #19301 Fixes #23580 RELNOTES: `override_repo` and `inject_repo` can be used to override and inject repos in module extensions. Closes #23534. PiperOrigin-RevId: 678139661 Change-Id: Iea7caca949c00e701f056c1037e273fee9740e93 (cherry picked from commit 46341b1) Fixes #23724 Fixes #23799 Also includes: * Disallow importing injected repos (8472c9d) --------- Co-authored-by: Xùdōng Yáng <[email protected]>
1 parent 56dd476 commit 067ce4d

23 files changed

+925
-57
lines changed

site/en/external/extension.md

+57
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,63 @@ several repo visibility rules:
176176
the repo visible to the module instead of the extension-generated repo
177177
of the same name.
178178

179+
### Overriding and injecting module extension repos
180+
181+
The root module can use
182+
[`override_repo`](/rules/lib/globals/module#override_repo) and
183+
[`inject_repo`](/rules/lib/globals/module#inject_repo) to override or inject
184+
module extension repos.
185+
186+
#### Example: Replacing `rules_java`'s `java_tools` with a vendored copy
187+
188+
```python
189+
# MODULE.bazel
190+
local_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository")
191+
local_repository(
192+
name = "my_java_tools",
193+
path = "vendor/java_tools",
194+
)
195+
196+
bazel_dep(name = "rules_java", version = "7.11.1")
197+
java_toolchains = use_extension("@rules_java//java:extension.bzl", "toolchains")
198+
199+
override_repo(java_toolchains, remote_java_tools = "my_java_tools")
200+
```
201+
202+
#### Example: Patch a Go dependency to depend on `@zlib` instead of the system zlib
203+
204+
```python
205+
# MODULE.bazel
206+
bazel_dep(name = "gazelle", version = "0.38.0")
207+
bazel_dep(name = "zlib", version = "1.3.1.bcr.3")
208+
209+
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
210+
go_deps.from_file(go_mod = "//:go.mod")
211+
go_deps.module_override(
212+
patches = [
213+
"//patches:my_module_zlib.patch",
214+
],
215+
path = "example.com/my_module",
216+
)
217+
use_repo(go_deps, ...)
218+
219+
inject_repo(go_deps, "zlib")
220+
```
221+
222+
```diff
223+
# patches/my_module_zlib.patch
224+
--- a/BUILD.bazel
225+
+++ b/BUILD.bazel
226+
@@ -1,6 +1,6 @@
227+
go_binary(
228+
name = "my_module",
229+
importpath = "example.com/my_module",
230+
srcs = ["my_module.go"],
231+
- copts = ["-lz"],
232+
+ cdeps = ["@zlib"],
233+
)
234+
```
235+
179236
## Best practices
180237

181238
This section describes best practices when writing extensions so they are

src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelDepGraphFunction.java

+46-3
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,24 @@ public SkyValue compute(SkyKey skyKey, Environment env)
7979
ImmutableBiMap<String, ModuleExtensionId> extensionUniqueNames =
8080
calculateUniqueNameForUsedExtensionId(extensionUsagesById, starlarkSemantics);
8181

82+
char repoNameSeparator =
83+
starlarkSemantics.getBool(BuildLanguageOptions.INCOMPATIBLE_USE_PLUS_IN_REPO_NAMES)
84+
? '+'
85+
: '~';
86+
8287
return BazelDepGraphValue.create(
8388
depGraph,
8489
canonicalRepoNameLookup,
8590
depGraph.values().stream().map(AbridgedModule::from).collect(toImmutableList()),
8691
extensionUsagesById,
8792
extensionUniqueNames.inverse(),
88-
starlarkSemantics.getBool(BuildLanguageOptions.INCOMPATIBLE_USE_PLUS_IN_REPO_NAMES)
89-
? '+'
90-
: '~');
93+
resolveRepoOverrides(
94+
depGraph,
95+
extensionUsagesById,
96+
extensionUniqueNames.inverse(),
97+
canonicalRepoNameLookup,
98+
repoNameSeparator),
99+
repoNameSeparator);
91100
}
92101

93102
private static ImmutableTable<ModuleExtensionId, ModuleKey, ModuleExtensionUsage>
@@ -218,6 +227,40 @@ private static String makeUniqueNameCandidate(
218227
+ extensionNameDisambiguator);
219228
}
220229

230+
private static ImmutableTable<ModuleExtensionId, String, RepositoryName> resolveRepoOverrides(
231+
ImmutableMap<ModuleKey, Module> depGraph,
232+
ImmutableTable<ModuleExtensionId, ModuleKey, ModuleExtensionUsage> extensionUsagesTable,
233+
ImmutableMap<ModuleExtensionId, String> extensionUniqueNames,
234+
ImmutableBiMap<RepositoryName, ModuleKey> canonicalRepoNameLookup,
235+
char repoNameSeparator) {
236+
RepositoryMapping rootModuleMappingWithoutOverrides =
237+
BazelDepGraphValue.getRepositoryMapping(
238+
ModuleKey.ROOT,
239+
depGraph,
240+
extensionUsagesTable,
241+
extensionUniqueNames,
242+
canonicalRepoNameLookup,
243+
// ModuleFileFunction ensures that repos that override other repos are not themselves
244+
// overridden, so we can safely pass an empty table here instead of resolving chains
245+
// of overrides.
246+
ImmutableTable.of(),
247+
repoNameSeparator);
248+
ImmutableTable.Builder<ModuleExtensionId, String, RepositoryName> repoOverridesBuilder =
249+
ImmutableTable.builder();
250+
for (var extensionId : extensionUsagesTable.rowKeySet()) {
251+
var rootUsage = extensionUsagesTable.row(extensionId).get(ModuleKey.ROOT);
252+
if (rootUsage != null) {
253+
for (var override : rootUsage.getRepoOverrides().entrySet()) {
254+
repoOverridesBuilder.put(
255+
extensionId,
256+
override.getKey(),
257+
rootModuleMappingWithoutOverrides.get(override.getValue().overridingRepoName()));
258+
}
259+
}
260+
}
261+
return repoOverridesBuilder.buildOrThrow();
262+
}
263+
221264
static class BazelDepGraphFunctionException extends SkyFunctionException {
222265
BazelDepGraphFunctionException(ExternalDepsException e, Transience transience) {
223266
super(e, transience);

src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelDepGraphValue.java

+38-6
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,15 @@ public static BazelDepGraphValue create(
4343
ImmutableList<AbridgedModule> abridgedModules,
4444
ImmutableTable<ModuleExtensionId, ModuleKey, ModuleExtensionUsage> extensionUsagesTable,
4545
ImmutableMap<ModuleExtensionId, String> extensionUniqueNames,
46+
ImmutableTable<ModuleExtensionId, String, RepositoryName> repoOverrides,
4647
char repoNameSeparator) {
4748
return new AutoValue_BazelDepGraphValue(
4849
depGraph,
4950
ImmutableBiMap.copyOf(canonicalRepoNameLookup),
5051
abridgedModules,
5152
extensionUsagesTable,
5253
extensionUniqueNames,
54+
repoOverrides,
5355
repoNameSeparator);
5456
}
5557

@@ -75,6 +77,7 @@ public static BazelDepGraphValue createEmptyDepGraph() {
7577
ImmutableList.of(),
7678
ImmutableTable.of(),
7779
ImmutableMap.of(),
80+
ImmutableTable.of(),
7881
'+');
7982
}
8083

@@ -107,6 +110,12 @@ public static BazelDepGraphValue createEmptyDepGraph() {
107110
*/
108111
public abstract ImmutableMap<ModuleExtensionId, String> getExtensionUniqueNames();
109112

113+
/**
114+
* For each module extension, a mapping from the name of the repo exported by the extension to the
115+
* canonical name of the repo that should override it (if any).
116+
*/
117+
public abstract ImmutableTable<ModuleExtensionId, String, RepositoryName> getRepoOverrides();
118+
110119
/** The character to use to separate the different segments of a canonical repo name. */
111120
public abstract char getRepoNameSeparator();
112121

@@ -115,22 +124,45 @@ public static BazelDepGraphValue createEmptyDepGraph() {
115124
* module deps and module extensions.
116125
*/
117126
public final RepositoryMapping getFullRepoMapping(ModuleKey key) {
127+
return getRepositoryMapping(
128+
key,
129+
getDepGraph(),
130+
getExtensionUsagesTable(),
131+
getExtensionUniqueNames(),
132+
getCanonicalRepoNameLookup(),
133+
getRepoOverrides(),
134+
getRepoNameSeparator());
135+
}
136+
137+
static RepositoryMapping getRepositoryMapping(
138+
ModuleKey key,
139+
ImmutableMap<ModuleKey, Module> depGraph,
140+
ImmutableTable<ModuleExtensionId, ModuleKey, ModuleExtensionUsage> extensionUsagesTable,
141+
ImmutableMap<ModuleExtensionId, String> extensionUniqueNames,
142+
ImmutableBiMap<RepositoryName, ModuleKey> canonicalRepoNameLookup,
143+
ImmutableTable<ModuleExtensionId, String, RepositoryName> repoOverrides,
144+
char repoNameSeparator) {
118145
ImmutableMap.Builder<String, RepositoryName> mapping = ImmutableMap.builder();
119146
for (Map.Entry<ModuleExtensionId, ModuleExtensionUsage> extIdAndUsage :
120-
getExtensionUsagesTable().column(key).entrySet()) {
147+
extensionUsagesTable.column(key).entrySet()) {
121148
ModuleExtensionId extensionId = extIdAndUsage.getKey();
122149
ModuleExtensionUsage usage = extIdAndUsage.getValue();
123-
String repoNamePrefix = getExtensionUniqueNames().get(extensionId) + getRepoNameSeparator();
150+
String repoNamePrefix = extensionUniqueNames.get(extensionId) + repoNameSeparator;
124151
for (ModuleExtensionUsage.Proxy proxy : usage.getProxies()) {
125152
for (Map.Entry<String, String> entry : proxy.getImports().entrySet()) {
126-
String canonicalRepoName = repoNamePrefix + entry.getValue();
127-
mapping.put(entry.getKey(), RepositoryName.createUnvalidated(canonicalRepoName));
153+
RepositoryName defaultCanonicalRepoName =
154+
RepositoryName.createUnvalidated(repoNamePrefix + entry.getValue());
155+
mapping.put(
156+
entry.getKey(),
157+
repoOverrides
158+
.row(extensionId)
159+
.getOrDefault(entry.getValue(), defaultCanonicalRepoName));
128160
}
129161
}
130162
}
131-
return getDepGraph()
163+
return depGraph
132164
.get(key)
133-
.getRepoMappingWithBazelDepsOnly(getCanonicalRepoNameLookup().inverse())
165+
.getRepoMappingWithBazelDepsOnly(canonicalRepoNameLookup.inverse())
134166
.withAdditionalMappings(mapping.buildOrThrow());
135167
}
136168
}

src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionEvalStarlarkThreadContext.java

+9-4
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ record RepoRuleCall(
6666
private final String repoPrefix;
6767
private final PackageIdentifier basePackageId;
6868
private final RepositoryMapping baseRepoMapping;
69+
private final ImmutableMap<String, RepositoryName> repoOverrides;
6970
private final BlazeDirectories directories;
7071
private final ExtendedEventHandler eventHandler;
7172
private final Map<String, RepoRuleCall> deferredRepos = new LinkedHashMap<>();
@@ -75,12 +76,14 @@ public ModuleExtensionEvalStarlarkThreadContext(
7576
String repoPrefix,
7677
PackageIdentifier basePackageId,
7778
RepositoryMapping baseRepoMapping,
79+
ImmutableMap<String, RepositoryName> repoOverrides,
7880
BlazeDirectories directories,
7981
ExtendedEventHandler eventHandler) {
8082
this.extensionId = extensionId;
8183
this.repoPrefix = repoPrefix;
8284
this.basePackageId = basePackageId;
8385
this.baseRepoMapping = baseRepoMapping;
86+
this.repoOverrides = repoOverrides;
8487
this.directories = directories;
8588
this.eventHandler = eventHandler;
8689
}
@@ -127,13 +130,15 @@ public ImmutableMap<String, RepoSpec> createRepos(StarlarkSemantics starlarkSema
127130
// Make it possible to refer to extension repos in the label attributes of another extension
128131
// repo. Wrapping a label in Label(...) ensures that it is evaluated with respect to the
129132
// containing module's repo mapping instead.
130-
var extensionRepos =
133+
ImmutableMap.Builder<String, RepositoryName> entries = ImmutableMap.builder();
134+
entries.putAll(baseRepoMapping.entries());
135+
entries.putAll(
131136
Maps.asMap(
132137
deferredRepos.keySet(),
133-
apparentName -> RepositoryName.createUnvalidated(repoPrefix + apparentName));
138+
apparentName -> RepositoryName.createUnvalidated(repoPrefix + apparentName)));
139+
entries.putAll(repoOverrides);
134140
RepositoryMapping fullRepoMapping =
135-
RepositoryMapping.create(extensionRepos, baseRepoMapping.ownerRepo())
136-
.withAdditionalMappings(baseRepoMapping);
141+
RepositoryMapping.create(entries.buildKeepingLast(), baseRepoMapping.ownerRepo());
137142
// LINT.ThenChange(//src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionRepoMappingEntriesFunction.java)
138143

139144
ImmutableMap.Builder<String, RepoSpec> repoSpecs = ImmutableMap.builder();

src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionRepoMappingEntriesFunction.java

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ private ModuleExtensionRepoMappingEntriesValue computeRepoMappingEntries(
7474
ImmutableMap.Builder<String, RepositoryName> entries = ImmutableMap.builder();
7575
entries.putAll(bazelDepGraphValue.getFullRepoMapping(moduleKey).entries());
7676
entries.putAll(extensionEvalValue.getCanonicalRepoNameToInternalNames().inverse());
77+
entries.putAll(bazelDepGraphValue.getRepoOverrides().row(extensionId));
7778
return ModuleExtensionRepoMappingEntriesValue.create(entries.buildKeepingLast(), moduleKey);
7879
// LINT.ThenChange(//src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionEvalStarlarkThreadContext.java)
7980
}

src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionUsage.java

+27
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.google.auto.value.AutoValue;
2020
import com.google.common.collect.ImmutableBiMap;
2121
import com.google.common.collect.ImmutableList;
22+
import com.google.common.collect.ImmutableMap;
2223
import com.google.devtools.build.lib.vfs.PathFragment;
2324
import com.google.errorprone.annotations.CanIgnoreReturnValue;
2425
import com.ryanharter.auto.value.gson.GenerateTypeAdapter;
@@ -130,6 +131,24 @@ public final boolean getHasNonDevUseExtension() {
130131
return getProxies().stream().anyMatch(p -> !p.isDevDependency());
131132
}
132133

134+
/**
135+
* Represents a repo that overrides another repo within the scope of the extension.
136+
*
137+
* @param overridingRepoName The apparent name of the overriding repo in the root module.
138+
* @param mustExist Whether this override should apply to an existing repo.
139+
* @param location The location of the {@code override_repo} or {@code inject_repo} call.
140+
*/
141+
@GenerateTypeAdapter
142+
public record RepoOverride(String overridingRepoName, boolean mustExist, Location location) {}
143+
144+
/**
145+
* Contains information about overrides that apply to repos generated by this extension. Keyed by
146+
* the extension-local repo name.
147+
*
148+
* <p>This is only non-empty for root module usages.
149+
*/
150+
public abstract ImmutableMap<String, RepoOverride> getRepoOverrides();
151+
133152
public abstract Builder toBuilder();
134153

135154
public static Builder builder() {
@@ -152,6 +171,11 @@ ModuleExtensionUsage trimForEvaluation() {
152171
// Extension implementation functions do not see the imports, they are only validated
153172
// against the set of generated repos in a validation step that comes afterward.
154173
.setProxies(ImmutableList.of())
174+
// Tracked in SingleExtensionUsagesValue instead, using canonical instead of apparent names.
175+
// Whether this override must apply to an existing repo as well as its source location also
176+
// don't influence the evaluation of the extension as they are checked in
177+
// SingleExtensionFunction.
178+
.setRepoOverrides(ImmutableMap.of())
155179
.build();
156180
}
157181

@@ -185,6 +209,9 @@ public Builder addTag(Tag value) {
185209
return this;
186210
}
187211

212+
@CanIgnoreReturnValue
213+
public abstract Builder setRepoOverrides(ImmutableMap<String, RepoOverride> repoOverrides);
214+
188215
public abstract ModuleExtensionUsage build();
189216
}
190217
}

src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleFileFunction.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ public static RootModuleFileValue evaluateRootModuleFile(
435435
try {
436436
module = moduleThreadContext.buildModule(/* registry= */ null);
437437
} catch (EvalException e) {
438-
eventHandler.handle(Event.error(e.getMessageWithStack()));
438+
eventHandler.handle(Event.error(e.getInnermostLocation(), e.getMessageWithStack()));
439439
throw errorf(Code.BAD_MODULE, "error executing MODULE.bazel file for the root module");
440440
}
441441
for (ModuleExtensionUsage usage : module.getExtensionUsages()) {
@@ -521,7 +521,7 @@ private static ModuleThreadContext execModuleFile(
521521
});
522522
compiledRootModuleFile.runOnThread(thread);
523523
} catch (EvalException e) {
524-
eventHandler.handle(Event.error(e.getMessageWithStack()));
524+
eventHandler.handle(Event.error(e.getInnermostLocation(), e.getMessageWithStack()));
525525
throw errorf(Code.BAD_MODULE, "error executing MODULE.bazel file for %s", moduleKey);
526526
}
527527
return context;

0 commit comments

Comments
 (0)