Skip to content

Commit e730201

Browse files
SalmaSamyWyverald
andauthored
[7.1.0] Reproducible extension (#21306)
- Allow module extension to declare it is reproducible and opt-out from the lockfile - Update bazelrc command from build to common to keep the same StarlarkSemantics for other commands - Update fetch option documentation --------- Co-authored-by: Xùdōng Yáng <[email protected]>
1 parent 9677ae2 commit e730201

File tree

9 files changed

+228
-57
lines changed

9 files changed

+228
-57
lines changed

.bazelrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ build --java_language_version=11
6161
build --tool_java_language_version=11
6262

6363
# Fail if a glob doesn't match anything (https://github.com/bazelbuild/bazel/issues/8195)
64-
build --incompatible_disallow_empty_glob
64+
common --incompatible_disallow_empty_glob
6565

6666
# Manually enable cc toolchain resolution before it is flipped. https://github.com/bazelbuild/bazel/issues/7260
6767
build --incompatible_enable_cc_toolchain_resolution

site/en/external/overview.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,18 @@ Normally, Bazel only fetches a repo when it needs something from the repo,
9494
and the repo hasn't already been fetched. If the repo has already been fetched
9595
before, Bazel only re-fetches it if its definition has changed.
9696

97+
The `fetch` command can be used to initiate a pre-fetch for a repository,
98+
target, or all necessary repositories to perform any build. This capability
99+
enables offline builds using the `--nofetch` option.
100+
101+
The `--fetch` option serves to manage network access. Its default value is true.
102+
However, when set to false (`--nofetch`), the command will utilize any cached
103+
version of the dependency, and if none exists, the command will result in
104+
failure.
105+
106+
See [fetch options](/reference/command-line-reference#fetch-options) for more
107+
information about controlling fetch.
108+
97109
### Directory layout {:#directory-layout}
98110

99111
After being fetched, the repo can be found in the subdirectory `external` in the

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,18 +121,23 @@ public void afterCommand() throws AbruptExitException {
121121

122122
// Add the new resolved extensions
123123
for (var event : extensionResolutionEventsMap.values()) {
124+
LockFileModuleExtension extension = event.getModuleExtension();
125+
if (!extension.shouldLockExtesnsion()) {
126+
continue;
127+
}
128+
124129
var oldExtensionEntries = updatedExtensionMap.get(event.getExtensionId());
125130
ImmutableMap<ModuleExtensionEvalFactors, LockFileModuleExtension> extensionEntries;
126131
if (oldExtensionEntries != null) {
127132
// extension exists, add the new entry to the existing map
128133
extensionEntries =
129134
new ImmutableMap.Builder<ModuleExtensionEvalFactors, LockFileModuleExtension>()
130135
.putAll(oldExtensionEntries)
131-
.put(event.getExtensionFactors(), event.getModuleExtension())
136+
.put(event.getExtensionFactors(), extension)
132137
.buildKeepingLast();
133138
} else {
134139
// new extension
135-
extensionEntries = ImmutableMap.of(event.getExtensionFactors(), event.getModuleExtension());
140+
extensionEntries = ImmutableMap.of(event.getExtensionFactors(), extension);
136141
}
137142
updatedExtensionMap.put(event.getExtensionId(), extensionEntries);
138143
}
@@ -164,12 +169,13 @@ private boolean shouldKeepExtension(
164169
// If there is a new event for this extension, compare it with the existing ones
165170
ModuleExtensionResolutionEvent extEvent = extensionResolutionEventsMap.get(extensionId);
166171
if (extEvent != null) {
172+
boolean doNotLockExtension = !extEvent.getModuleExtension().shouldLockExtesnsion();
167173
boolean dependencyOnOsChanged =
168174
lockedExtensionKey.getOs().isEmpty() != extEvent.getExtensionFactors().getOs().isEmpty();
169175
boolean dependencyOnArchChanged =
170176
lockedExtensionKey.getArch().isEmpty()
171177
!= extEvent.getExtensionFactors().getArch().isEmpty();
172-
if (dependencyOnOsChanged || dependencyOnArchChanged) {
178+
if (doNotLockExtension || dependencyOnOsChanged || dependencyOnArchChanged) {
173179
return false;
174180
}
175181
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ public static Builder builder() {
5454

5555
public abstract Builder toBuilder();
5656

57+
public boolean shouldLockExtesnsion() {
58+
return getModuleExtensionMetadata().isEmpty()
59+
|| !getModuleExtensionMetadata().get().getReproducible();
60+
}
61+
5762
/** Builder type for {@link LockFileModuleExtension}. */
5863
@AutoValue.Builder
5964
public abstract static class Builder {

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,24 @@ public boolean rootModuleHasNonDevDependency() {
206206
@ParamType(type = String.class),
207207
@ParamType(type = NoneType.class)
208208
}),
209+
@Param(
210+
name = "reproducible",
211+
doc =
212+
"States that this module extension ensures complete reproducibility, thereby it "
213+
+ "should not be stored in the lockfile.",
214+
positional = false,
215+
named = true,
216+
defaultValue = "False",
217+
allowedTypes = {
218+
@ParamType(type = Boolean.class),
219+
}),
209220
})
210221
public ModuleExtensionMetadata extensionMetadata(
211-
Object rootModuleDirectDepsUnchecked, Object rootModuleDirectDevDepsUnchecked)
222+
Object rootModuleDirectDepsUnchecked,
223+
Object rootModuleDirectDevDepsUnchecked,
224+
boolean reproducible)
212225
throws EvalException {
213226
return ModuleExtensionMetadata.create(
214-
rootModuleDirectDepsUnchecked, rootModuleDirectDevDepsUnchecked);
227+
rootModuleDirectDepsUnchecked, rootModuleDirectDevDepsUnchecked, reproducible);
215228
}
216229
}

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

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,38 +63,44 @@ public abstract class ModuleExtensionMetadata implements StarlarkValue {
6363

6464
abstract UseAllRepos getUseAllRepos();
6565

66+
abstract boolean getReproducible();
67+
6668
private static ModuleExtensionMetadata create(
6769
@Nullable Set<String> explicitRootModuleDirectDeps,
6870
@Nullable Set<String> explicitRootModuleDirectDevDeps,
69-
UseAllRepos useAllRepos) {
71+
UseAllRepos useAllRepos,
72+
boolean reproducible) {
7073
return new AutoValue_ModuleExtensionMetadata(
7174
explicitRootModuleDirectDeps != null
7275
? ImmutableSet.copyOf(explicitRootModuleDirectDeps)
7376
: null,
7477
explicitRootModuleDirectDevDeps != null
7578
? ImmutableSet.copyOf(explicitRootModuleDirectDevDeps)
7679
: null,
77-
useAllRepos);
80+
useAllRepos,
81+
reproducible);
7882
}
7983

8084
static ModuleExtensionMetadata create(
81-
Object rootModuleDirectDepsUnchecked, Object rootModuleDirectDevDepsUnchecked)
85+
Object rootModuleDirectDepsUnchecked,
86+
Object rootModuleDirectDevDepsUnchecked,
87+
boolean reproducible)
8288
throws EvalException {
8389
if (rootModuleDirectDepsUnchecked == Starlark.NONE
8490
&& rootModuleDirectDevDepsUnchecked == Starlark.NONE) {
85-
return create(null, null, UseAllRepos.NO);
91+
return create(null, null, UseAllRepos.NO, reproducible);
8692
}
8793

8894
// When root_module_direct_deps = "all", accept both root_module_direct_dev_deps = None and
8995
// root_module_direct_dev_deps = [], but not root_module_direct_dev_deps = ["some_repo"].
9096
if (rootModuleDirectDepsUnchecked.equals("all")
9197
&& rootModuleDirectDevDepsUnchecked.equals(StarlarkList.immutableOf())) {
92-
return create(null, null, UseAllRepos.REGULAR);
98+
return create(null, null, UseAllRepos.REGULAR, reproducible);
9399
}
94100

95101
if (rootModuleDirectDevDepsUnchecked.equals("all")
96102
&& rootModuleDirectDepsUnchecked.equals(StarlarkList.immutableOf())) {
97-
return create(null, null, UseAllRepos.DEV);
103+
return create(null, null, UseAllRepos.DEV, reproducible);
98104
}
99105

100106
if (rootModuleDirectDepsUnchecked.equals("all")
@@ -152,7 +158,11 @@ static ModuleExtensionMetadata create(
152158
}
153159
}
154160

155-
return create(explicitRootModuleDirectDeps, explicitRootModuleDirectDevDeps, UseAllRepos.NO);
161+
return create(
162+
explicitRootModuleDirectDeps,
163+
explicitRootModuleDirectDevDeps,
164+
UseAllRepos.NO,
165+
reproducible);
156166
}
157167

158168
public void evaluate(

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

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -152,20 +152,27 @@ public SkyValue compute(SkyKey skyKey, Environment env)
152152
}
153153

154154
// Check the lockfile first for that module extension
155+
LockFileModuleExtension lockedExtension = null;
155156
LockfileMode lockfileMode = BazelLockFileFunction.LOCKFILE_MODE.get(env);
156157
if (!lockfileMode.equals(LockfileMode.OFF)) {
157158
BazelLockFileValue lockfile = (BazelLockFileValue) env.getValue(BazelLockFileValue.KEY);
158159
if (lockfile == null) {
159160
return null;
160161
}
161-
try {
162-
SingleExtensionEvalValue singleExtensionEvalValue =
163-
tryGettingValueFromLockFile(env, extensionId, extension, usagesValue, lockfile);
164-
if (singleExtensionEvalValue != null) {
165-
return singleExtensionEvalValue;
162+
var lockedExtensionMap = lockfile.getModuleExtensions().get(extensionId);
163+
lockedExtension =
164+
lockedExtensionMap == null ? null : lockedExtensionMap.get(extension.getEvalFactors());
165+
if (lockedExtension != null) {
166+
try {
167+
SingleExtensionEvalValue singleExtensionEvalValue =
168+
tryGettingValueFromLockFile(
169+
env, extensionId, extension, usagesValue, lockfile, lockedExtension);
170+
if (singleExtensionEvalValue != null) {
171+
return singleExtensionEvalValue;
172+
}
173+
} catch (NeedsSkyframeRestartException e) {
174+
return null;
166175
}
167-
} catch (NeedsSkyframeRestartException e) {
168-
return null;
169176
}
170177
}
171178

@@ -182,6 +189,24 @@ public SkyValue compute(SkyKey skyKey, Environment env)
182189
Optional<ModuleExtensionMetadata> moduleExtensionMetadata =
183190
moduleExtensionResult.getModuleExtensionMetadata();
184191

192+
if (lockfileMode.equals(LockfileMode.ERROR)) {
193+
boolean extensionShouldHaveBeenLocked =
194+
moduleExtensionMetadata.map(metadata -> !metadata.getReproducible()).orElse(true);
195+
// If this extension was not found in the lockfile, and after evaluation we found that it is
196+
// not reproducible, then error indicating that it was expected to be in the lockfile.
197+
if (lockedExtension == null && extensionShouldHaveBeenLocked) {
198+
throw new SingleExtensionEvalFunctionException(
199+
ExternalDepsException.withMessage(
200+
Code.BAD_MODULE,
201+
"The module extension '%s'%s does not exist in the lockfile",
202+
extensionId,
203+
extension.getEvalFactors().isEmpty()
204+
? ""
205+
: " for platform " + extension.getEvalFactors()),
206+
Transience.PERSISTENT);
207+
}
208+
}
209+
185210
// At this point the extension has been evaluated successfully, but SingleExtensionEvalFunction
186211
// may still fail if imported repositories were not generated. However, since imports do not
187212
// influence the evaluation of the extension and the validation also runs when the extension
@@ -220,30 +245,13 @@ private SingleExtensionEvalValue tryGettingValueFromLockFile(
220245
ModuleExtensionId extensionId,
221246
RunnableExtension extension,
222247
SingleExtensionUsagesValue usagesValue,
223-
BazelLockFileValue lockfile)
248+
BazelLockFileValue lockfile,
249+
LockFileModuleExtension lockedExtension)
224250
throws SingleExtensionEvalFunctionException,
225251
InterruptedException,
226252
NeedsSkyframeRestartException {
227253
LockfileMode lockfileMode = BazelLockFileFunction.LOCKFILE_MODE.get(env);
228254

229-
var lockedExtensionMap = lockfile.getModuleExtensions().get(extensionId);
230-
LockFileModuleExtension lockedExtension =
231-
lockedExtensionMap == null ? null : lockedExtensionMap.get(extension.getEvalFactors());
232-
if (lockedExtension == null) {
233-
if (lockfileMode.equals(LockfileMode.ERROR)) {
234-
throw new SingleExtensionEvalFunctionException(
235-
ExternalDepsException.withMessage(
236-
Code.BAD_MODULE,
237-
"The module extension '%s'%s does not exist in the lockfile",
238-
extensionId,
239-
extension.getEvalFactors().isEmpty()
240-
? ""
241-
: " for platform " + extension.getEvalFactors()),
242-
Transience.PERSISTENT);
243-
}
244-
return null;
245-
}
246-
247255
ImmutableMap<ModuleKey, ModuleExtensionUsage> lockedExtensionUsages;
248256
try {
249257
// TODO(salmasamy) might be nicer to precompute this table when we construct

src/main/java/com/google/devtools/build/lib/pkgcache/PackageOptions.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,14 @@ public ParallelismConverter() throws OptionsParsingException {
173173
public int maxDirectoriesToEagerlyVisitInGlobbing;
174174

175175
@Option(
176-
name = "fetch",
177-
defaultValue = "true",
178-
documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
179-
effectTags = {OptionEffectTag.UNKNOWN},
180-
help = "Allows the command to fetch external dependencies"
181-
)
176+
name = "fetch",
177+
defaultValue = "true",
178+
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
179+
effectTags = {OptionEffectTag.UNKNOWN},
180+
help =
181+
"Allows the command to fetch external dependencies. If set to false, the command will"
182+
+ " utilize any cached version of the dependency, and if none exists, the command"
183+
+ " will result in failure.")
182184
public boolean fetch;
183185

184186
@Option(

0 commit comments

Comments
 (0)