[6.4.0] Lockfile updates & fixes (#19153) (#19175)
* Add 'environ' attribute to module extensions and update lockfile
- Add 'environ' to module extensions
- Store the variables and their values in the lockfile
- Compare the variables and values with the lockfile and re-evaluate or error if they differ
PiperOrigin-RevId: 548964193
Change-Id: Ic2d52fe3332e93095c414d8bca4c9b4312bca8c2
# Conflicts:
# src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtension.java
# src/main/java/com/google/devtools/build/lib/bazel/bzlmod/SingleExtensionEvalFunction.java
# src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryModule.java
# src/main/java/com/google/devtools/build/lib/skyframe/ActionEnvironmentFunction.java
# src/main/java/com/google/devtools/build/lib/skyframe/ClientEnvironmentFunction.java
# src/main/java/com/google/devtools/build/lib/starlarkbuildapi/repository/RepositoryModuleApi.java
# src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/FakeRepositoryModule.java
# src/test/java/com/google/devtools/build/lib/bazel/bzlmod/StarlarkBazelModuleTest.java
# src/test/py/bazel/bzlmod/bazel_lockfile_test.py
* Remove extra changes
* Re-run extension if its files change
fixes https://github.com/bazelbuild/bazel/issues/19068
PiperOrigin-RevId: 552823534
Change-Id: I87256b2cf954b932e24c70e22386020599f21a6f
* Do not fail on new fields added to lockfile data
Fixes https://github.com/bazelbuild/bazel/issues/19105
PiperOrigin-RevId: 553068023
Change-Id: I877bc8ece0641c01119a9295e09175a2d0a3a0c1
---------
Co-authored-by: Salma Samy <salmasamy@google.com>
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileValue.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileValue.java
index c0d0c06..26c674f 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileValue.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelLockFileValue.java
@@ -109,25 +109,34 @@
return moduleDiff.build();
}
+ /** Returns the differences between an extension and its locked data */
public ImmutableList<String> getModuleExtensionDiff(
- LockFileModuleExtension lockedExtension,
- ImmutableMap<ModuleKey, ModuleExtensionUsage> lockedExtensionUsages,
ModuleExtensionId extensionId,
byte[] transitiveDigest,
- ImmutableMap<ModuleKey, ModuleExtensionUsage> extensionUsages) {
+ boolean filesChanged,
+ ImmutableMap<String, String> envVariables,
+ ImmutableMap<ModuleKey, ModuleExtensionUsage> extensionUsages,
+ ImmutableMap<ModuleKey, ModuleExtensionUsage> lockedExtensionUsages) {
+ LockFileModuleExtension lockedExtension = getModuleExtensions().get(extensionId);
+
ImmutableList.Builder<String> extDiff = new ImmutableList.Builder<>();
- if (lockedExtension == null) {
- extDiff.add("The module extension '" + extensionId + "' does not exist in the lockfile");
- } else {
- if (!Arrays.equals(transitiveDigest, lockedExtension.getBzlTransitiveDigest())) {
+ if (!Arrays.equals(transitiveDigest, lockedExtension.getBzlTransitiveDigest())) {
extDiff.add(
"The implementation of the extension '"
+ extensionId
+ "' or one of its transitive .bzl files has changed");
- }
- if (!extensionUsages.equals(lockedExtensionUsages)) {
- extDiff.add("The usages of the extension named '" + extensionId + "' has changed");
- }
+ }
+ if (filesChanged) {
+ extDiff.add("One or more files the extension '" + extensionId + "' is using have changed");
+ }
+ if (!extensionUsages.equals(lockedExtensionUsages)) {
+ extDiff.add("The usages of the extension '" + extensionId + "' has changed");
+ }
+ if (!envVariables.equals(lockedExtension.getEnvVariables())) {
+ extDiff.add(
+ "The environment variables the extension '"
+ + extensionId
+ + "' depends on (or their values) have changed");
}
return extDiff.build();
}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/GsonTypeAdapterUtil.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/GsonTypeAdapterUtil.java
index f0190f4..3344847 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/GsonTypeAdapterUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/GsonTypeAdapterUtil.java
@@ -91,6 +91,19 @@
}
};
+ public static final TypeAdapter<Label> LABEL_TYPE_ADAPTER =
+ new TypeAdapter<Label>() {
+ @Override
+ public void write(JsonWriter jsonWriter, Label label) throws IOException {
+ jsonWriter.value(label.getUnambiguousCanonicalForm());
+ }
+
+ @Override
+ public Label read(JsonReader jsonReader) throws IOException {
+ return Label.parseCanonicalUnchecked(jsonReader.nextString());
+ }
+ };
+
public static final TypeAdapter<ModuleExtensionId> MODULE_EXTENSION_ID_TYPE_ADAPTER =
new TypeAdapter<>() {
@Override
@@ -308,6 +321,7 @@
.registerTypeAdapterFactory(IMMUTABLE_SET)
.registerTypeAdapterFactory(OPTIONAL)
.registerTypeAdapterFactory(new LocationTypeAdapterFactory(moduleFilePath))
+ .registerTypeAdapter(Label.class, LABEL_TYPE_ADAPTER)
.registerTypeAdapter(Version.class, VERSION_TYPE_ADAPTER)
.registerTypeAdapter(ModuleKey.class, MODULE_KEY_TYPE_ADAPTER)
.registerTypeAdapter(ModuleExtensionId.class, MODULE_EXTENSION_ID_TYPE_ADAPTER)
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/LockFileModuleExtension.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/LockFileModuleExtension.java
index 5431d6c..1b5eb71 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/LockFileModuleExtension.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/LockFileModuleExtension.java
@@ -16,6 +16,7 @@
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.events.ExtendedEventHandler.Postable;
import com.ryanharter.auto.value.gson.GenerateTypeAdapter;
@@ -28,13 +29,36 @@
@GenerateTypeAdapter
public abstract class LockFileModuleExtension implements Postable {
+ public static Builder builder() {
+ return new AutoValue_LockFileModuleExtension.Builder()
+ // TODO(salmasamy) can be removed when updating lockfile version
+ .setEnvVariables(ImmutableMap.of())
+ .setAccumulatedFileDigests(ImmutableMap.of());
+ }
+
@SuppressWarnings("mutable")
public abstract byte[] getBzlTransitiveDigest();
+ public abstract ImmutableMap<Label, String> getAccumulatedFileDigests();
+
+ public abstract ImmutableMap<String, String> getEnvVariables();
+
public abstract ImmutableMap<String, RepoSpec> getGeneratedRepoSpecs();
- public static LockFileModuleExtension create(
- byte[] transitiveDigest, ImmutableMap<String, RepoSpec> generatedRepoSpecs) {
- return new AutoValue_LockFileModuleExtension(transitiveDigest, generatedRepoSpecs);
+ public abstract Builder toBuilder();
+
+ /** Builder type for {@link LockFileModuleExtension}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ public abstract Builder setBzlTransitiveDigest(byte[] digest);
+
+ public abstract Builder setAccumulatedFileDigests(ImmutableMap<Label, String> value);
+
+ public abstract Builder setEnvVariables(ImmutableMap<String, String> value);
+
+ public abstract Builder setGeneratedRepoSpecs(ImmutableMap<String, RepoSpec> value);
+
+ public abstract LockFileModuleExtension build();
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtension.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtension.java
index a55de95..99219ef 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtension.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtension.java
@@ -16,6 +16,7 @@
import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.events.EventHandler;
@@ -23,6 +24,7 @@
import javax.annotation.Nullable;
import net.starlark.java.annot.StarlarkBuiltin;
import net.starlark.java.eval.StarlarkCallable;
+import net.starlark.java.eval.StarlarkValue;
import net.starlark.java.syntax.Location;
/**
@@ -30,7 +32,7 @@
* or register toolchains and execution platforms.
*/
@AutoValue
-public abstract class ModuleExtension {
+public abstract class ModuleExtension implements StarlarkValue {
public abstract String getName();
public abstract StarlarkCallable getImplementation();
@@ -43,6 +45,8 @@
public abstract Location getLocation();
+ public abstract ImmutableList<String> getEnvVariables();
+
public static Builder builder() {
return new AutoValue_ModuleExtension.Builder();
}
@@ -63,6 +67,8 @@
public abstract Builder setTagClasses(ImmutableMap<String, TagClass> value);
+ public abstract Builder setEnvVariables(ImmutableList<String> value);
+
public abstract ModuleExtension build();
}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/SingleExtensionEvalFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/SingleExtensionEvalFunction.java
index 8bb26e5..4d72d02 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/SingleExtensionEvalFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/bzlmod/SingleExtensionEvalFunction.java
@@ -18,10 +18,12 @@
import static com.google.common.collect.ImmutableBiMap.toImmutableBiMap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableTable;
+import com.google.devtools.build.lib.actions.FileValue;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.bazel.repository.RepositoryOptions.LockfileMode;
import com.google.devtools.build.lib.bazel.repository.downloader.DownloadManager;
@@ -32,6 +34,7 @@
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.rules.repository.NeedsSkyframeRestartException;
+import com.google.devtools.build.lib.rules.repository.RepositoryFunction;
import com.google.devtools.build.lib.runtime.ProcessWrapper;
import com.google.devtools.build.lib.runtime.RepositoryRemoteExecutor;
import com.google.devtools.build.lib.server.FailureDetails.ExternalDeps;
@@ -41,20 +44,24 @@
import com.google.devtools.build.lib.skyframe.BzlLoadValue;
import com.google.devtools.build.lib.skyframe.PrecomputedValue;
import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyFunctionException;
import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
+import com.google.devtools.build.skyframe.SkyframeLookupResult;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import net.starlark.java.eval.EvalException;
+import net.starlark.java.eval.Module;
import net.starlark.java.eval.Mutability;
import net.starlark.java.eval.Starlark;
import net.starlark.java.eval.StarlarkList;
@@ -144,9 +151,16 @@
Transience.PERSISTENT);
}
- // Check the lockfile first for that module extension
+ ModuleExtension extension = ((ModuleExtension.InStarlark) exported).get();
+ ImmutableMap<String, String> extensionEnvVars =
+ RepositoryFunction.getEnvVarValues(env, extension.getEnvVariables());
+ if (extensionEnvVars == null) {
+ return null;
+ }
byte[] bzlTransitiveDigest =
BazelModuleContext.of(bzlLoadValue.getModule()).bzlTransitiveDigest();
+
+ // Check the lockfile first for that module extension
LockfileMode lockfileMode = BazelLockFileFunction.LOCKFILE_MODE.get(env);
if (!lockfileMode.equals(LockfileMode.OFF)) {
BazelLockFileValue lockfile = (BazelLockFileValue) env.getValue(BazelLockFileValue.KEY);
@@ -155,20 +169,27 @@
}
SingleExtensionEvalValue singleExtensionEvalValue =
tryGettingValueFromLockFile(
- extensionId, usagesValue, bzlTransitiveDigest, lockfileMode, lockfile);
+ env,
+ extensionId,
+ extensionEnvVars,
+ usagesValue,
+ bzlTransitiveDigest,
+ lockfileMode,
+ lockfile);
if (singleExtensionEvalValue != null) {
return singleExtensionEvalValue;
}
}
// Run that extension!
- ModuleExtension extension = ((ModuleExtension.InStarlark) exported).get();
- ImmutableMap<String, RepoSpec> generatedRepoSpecs =
+ RunModuleExtensionResult moduleExtensionResult =
runModuleExtension(
extensionId, extension, usagesValue, bzlLoadValue.getModule(), starlarkSemantics, env);
- if (generatedRepoSpecs == null) {
+ if (moduleExtensionResult == null) {
return null;
}
+ ImmutableMap<String, RepoSpec> generatedRepoSpecs =
+ moduleExtensionResult.getGeneratedRepoSpecs();
// Check that all imported repos have been actually generated
validateAllImportsAreGenerated(generatedRepoSpecs, usagesValue, extensionId);
@@ -177,20 +198,39 @@
.post(
ModuleExtensionResolutionEvent.create(
extensionId,
- LockFileModuleExtension.create(bzlTransitiveDigest, generatedRepoSpecs)));
+ LockFileModuleExtension.builder()
+ .setBzlTransitiveDigest(bzlTransitiveDigest)
+ .setAccumulatedFileDigests(moduleExtensionResult.getAccumulatedFileDigests())
+ .setEnvVariables(extensionEnvVars)
+ .setGeneratedRepoSpecs(generatedRepoSpecs)
+ .build()));
}
return createSingleExtentionValue(generatedRepoSpecs, usagesValue);
}
@Nullable
private SingleExtensionEvalValue tryGettingValueFromLockFile(
+ Environment env,
ModuleExtensionId extensionId,
+ ImmutableMap<String, String> envVariables,
SingleExtensionUsagesValue usagesValue,
byte[] bzlTransitiveDigest,
LockfileMode lockfileMode,
BazelLockFileValue lockfile)
- throws SingleExtensionEvalFunctionException {
+ throws SingleExtensionEvalFunctionException, InterruptedException {
LockFileModuleExtension lockedExtension = lockfile.getModuleExtensions().get(extensionId);
+ if (lockedExtension == null) {
+ if (lockfileMode.equals(LockfileMode.ERROR)) {
+ throw new SingleExtensionEvalFunctionException(
+ ExternalDepsException.withMessage(
+ Code.BAD_MODULE,
+ "The module extension '%s' does not exist in the lockfile",
+ extensionId),
+ Transience.PERSISTENT);
+ }
+ return null;
+ }
+
ImmutableMap<ModuleKey, ModuleExtensionUsage> lockedExtensionUsages;
try {
// TODO(salmasamy) might be nicer to precompute this table when we construct
@@ -202,19 +242,26 @@
throw new SingleExtensionEvalFunctionException(e, Transience.PERSISTENT);
}
- // If we have the extension, check if the implementation and usage haven't changed
- if (lockedExtension != null
+ Boolean filesChanged = didFilesChange(env, lockedExtension.getAccumulatedFileDigests());
+ if (filesChanged == null) { // still calculating file changes
+ return null;
+ }
+
+ // Check extension data in lockfile still valid
+ if (!filesChanged
&& Arrays.equals(bzlTransitiveDigest, lockedExtension.getBzlTransitiveDigest())
- && usagesValue.getExtensionUsages().equals(lockedExtensionUsages)) {
+ && usagesValue.getExtensionUsages().equals(lockedExtensionUsages)
+ && envVariables.equals(lockedExtension.getEnvVariables())) {
return createSingleExtentionValue(lockedExtension.getGeneratedRepoSpecs(), usagesValue);
} else if (lockfileMode.equals(LockfileMode.ERROR)) {
ImmutableList<String> extDiff =
lockfile.getModuleExtensionDiff(
- lockedExtension,
- lockedExtensionUsages,
extensionId,
bzlTransitiveDigest,
- usagesValue.getExtensionUsages());
+ filesChanged,
+ envVariables,
+ usagesValue.getExtensionUsages(),
+ lockedExtensionUsages);
throw new SingleExtensionEvalFunctionException(
ExternalDepsException.withMessage(
Code.BAD_MODULE,
@@ -225,6 +272,43 @@
return null;
}
+ @Nullable
+ private Boolean didFilesChange(
+ Environment env, ImmutableMap<Label, String> accumulatedFileDigests)
+ throws InterruptedException {
+ // Turn labels into FileValue keys & get those values
+ Map<Label, FileValue.Key> fileKeys = new HashMap<>();
+ for (Label label : accumulatedFileDigests.keySet()) {
+ try {
+ RootedPath rootedPath = RepositoryFunction.getRootedPathFromLabel(label, env);
+ fileKeys.put(label, FileValue.key(rootedPath));
+ } catch (NeedsSkyframeRestartException e) {
+ return null;
+ } catch (EvalException e) {
+ // Consider those exception to be a cause for invalidation
+ return true;
+ }
+ }
+ SkyframeLookupResult result = env.getValuesAndExceptions(fileKeys.values());
+ if (env.valuesMissing()) {
+ return null;
+ }
+
+ // Compare the collected file values with the hashes stored in the lockfile
+ for (Entry<Label, String> entry : accumulatedFileDigests.entrySet()) {
+ FileValue fileValue = (FileValue) result.get(fileKeys.get(entry.getKey()));
+ try {
+ if (!entry.getValue().equals(RepositoryFunction.fileValueToMarkerValue(fileValue))) {
+ return true;
+ }
+ } catch (IOException e) {
+ // Consider those exception to be a cause for invalidation
+ return true;
+ }
+ }
+ return false;
+ }
+
private SingleExtensionEvalValue createSingleExtentionValue(
ImmutableMap<String, RepoSpec> generatedRepoSpecs, SingleExtensionUsagesValue usagesValue) {
return SingleExtensionEvalValue.create(
@@ -301,11 +385,11 @@
}
@Nullable
- private ImmutableMap<String, RepoSpec> runModuleExtension(
+ private RunModuleExtensionResult runModuleExtension(
ModuleExtensionId extensionId,
ModuleExtension extension,
SingleExtensionUsagesValue usagesValue,
- net.starlark.java.eval.Module module,
+ Module module,
StarlarkSemantics starlarkSemantics,
Environment env)
throws SingleExtensionEvalFunctionException, InterruptedException {
@@ -316,12 +400,12 @@
BazelModuleContext.of(module).repoMapping(),
directories,
env.getListener());
+ ModuleExtensionContext moduleContext;
try (Mutability mu =
Mutability.create("module extension", usagesValue.getExtensionUniqueName())) {
StarlarkThread thread = new StarlarkThread(mu, starlarkSemantics);
thread.setPrintHandler(Event.makeDebugPrintHandler(env.getListener()));
- ModuleExtensionContext moduleContext =
- createContext(env, usagesValue, starlarkSemantics, extensionId, extension);
+ moduleContext = createContext(env, usagesValue, starlarkSemantics, extensionId, extension);
threadContext.storeInThread(thread);
try {
Object returnValue =
@@ -371,7 +455,8 @@
Transience.TRANSIENT);
}
}
- return threadContext.getGeneratedRepoSpecs();
+ return RunModuleExtensionResult.create(
+ moduleContext.getAccumulatedFileDigests(), threadContext.getGeneratedRepoSpecs());
}
private ModuleExtensionContext createContext(
@@ -423,4 +508,22 @@
super(cause, transience);
}
}
+
+ /* Holds the result data from running a module extension */
+ @AutoValue
+ abstract static class RunModuleExtensionResult {
+
+ abstract ImmutableMap<Label, String> getAccumulatedFileDigests();
+
+ abstract ImmutableMap<String, RepoSpec> getGeneratedRepoSpecs();
+
+ static RunModuleExtensionResult create(
+ ImmutableMap<Label, String> accumulatedFileDigests,
+ ImmutableMap<String, RepoSpec> generatedRepoSpecs) {
+ return new AutoValue_SingleExtensionEvalFunction_RunModuleExtensionResult(
+ accumulatedFileDigests, generatedRepoSpecs);
+ }
+ }
}
+
+
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryModule.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryModule.java
index 3dd3e9d..540b556 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryModule.java
@@ -296,6 +296,15 @@
"A description of the module extension that can be extracted by documentation"
+ " generating tools.",
named = true,
+ positional = false),
+ @Param(
+ name = "environ",
+ defaultValue = "[]",
+ doc =
+ "Provides a list of environment variable that this module extension depends on. If "
+ + "an environment variable in that list changes, the extension will be "
+ + "re-evaluated.",
+ named = true,
positional = false)
},
useStarlarkThread = true)
@@ -303,6 +312,7 @@
StarlarkCallable implementation,
Dict<?, ?> tagClasses, // Dict<String, TagClass>
String doc,
+ Sequence<?> environ, // <String>
StarlarkThread thread)
throws EvalException {
ModuleExtension.InStarlark inStarlark = new ModuleExtension.InStarlark();
@@ -314,6 +324,7 @@
.setDoc(doc)
.setDefinitionEnvironmentLabel(
BazelModuleContext.of(Module.ofInnermostEnclosingStarlarkFunction(thread)).label())
+ .setEnvVariables(ImmutableList.copyOf(Sequence.cast(environ, String.class, "environ")))
.setLocation(thread.getCallerLocation());
return inStarlark;
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryFunction.java b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryFunction.java
index 55084b9..26d04ae 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryFunction.java
@@ -20,6 +20,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.io.BaseEncoding;
import com.google.devtools.build.lib.actions.FileValue;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
@@ -50,6 +51,7 @@
import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
import com.google.devtools.build.skyframe.SkyKey;
import java.io.IOException;
+import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
@@ -170,9 +172,9 @@
throws InterruptedException, RepositoryFunctionException;
@SuppressWarnings("unchecked")
- private static Iterable<String> getEnviron(Rule rule) {
+ private static Collection<String> getEnviron(Rule rule) {
if (rule.isAttrDefined("$environ", Type.STRING_LIST)) {
- return (Iterable<String>) rule.getAttr("$environ");
+ return (Collection<String>) rule.getAttr("$environ");
}
return ImmutableList.of();
}
@@ -183,7 +185,7 @@
* is needed.
*/
public boolean verifyMarkerData(Rule rule, Map<String, String> markerData, Environment env)
- throws InterruptedException, RepositoryFunctionException {
+ throws InterruptedException {
return verifyEnvironMarkerData(markerData, env, getEnviron(rule))
&& verifyMarkerDataForFiles(rule, markerData, env)
&& verifySemanticsMarkerData(markerData, env);
@@ -288,33 +290,37 @@
protected Map<String, String> declareEnvironmentDependencies(
Map<String, String> markerData, Environment env, Iterable<String> keys)
throws InterruptedException {
- Map<String, String> environ = ActionEnvironmentFunction.getEnvironmentView(env, keys);
+ ImmutableMap<String, String> envDep = getEnvVarValues(env, keys);
+ if (envDep == null) {
+ return null;
+ }
+ // Add the dependencies to the marker file
+ keys.forEach(key -> markerData.put("ENV:" + key, envDep.get(key)));
+ return envDep;
+ }
- // Returns true if there is a null value and we need to wait for some dependencies.
+ @Nullable
+ public static ImmutableMap<String, String> getEnvVarValues(Environment env, Iterable<String> keys)
+ throws InterruptedException {
+ ImmutableMap<String, String> environ = ActionEnvironmentFunction.getEnvironmentView(env, keys);
if (environ == null) {
return null;
}
-
Map<String, String> repoEnvOverride = PrecomputedValue.REPO_ENV.get(env);
if (repoEnvOverride == null) {
return null;
}
// Only depend on --repo_env values that are specified in the "environ" attribute.
- Map<String, String> repoEnv = new LinkedHashMap<String, String>(environ);
+ ImmutableMap.Builder<String, String> repoEnv = ImmutableMap.builder();
+ repoEnv.putAll(environ);
for (String key : keys) {
String value = repoEnvOverride.get(key);
if (value != null) {
repoEnv.put(key, value);
}
}
-
- // Add the dependencies to the marker file
- for (Map.Entry<String, String> value : repoEnv.entrySet()) {
- markerData.put("ENV:" + value.getKey(), value.getValue());
- }
-
- return repoEnv;
+ return repoEnv.buildKeepingLast();
}
/**
@@ -323,9 +329,9 @@
* Environment)} function to verify the values for environment variables.
*/
protected boolean verifyEnvironMarkerData(
- Map<String, String> markerData, Environment env, Iterable<String> keys)
+ Map<String, String> markerData, Environment env, Collection<String> keys)
throws InterruptedException {
- Map<String, String> environ = ActionEnvironmentFunction.getEnvironmentView(env, keys);
+ ImmutableMap<String, String> environ = ActionEnvironmentFunction.getEnvironmentView(env, keys);
if (env.valuesMissing()) {
return false; // Returns false so caller knows to return immediately
}
@@ -346,17 +352,18 @@
// Verify that all environment variable in the marker file are also in keys
for (String key : markerData.keySet()) {
- if (key.startsWith("ENV:") && !repoEnv.containsKey(key.substring(4))) {
+ if (key.startsWith("ENV:") && !keys.contains(key.substring(4))) {
return false;
}
}
+
// Now verify the values of the marker data
- for (Map.Entry<String, String> value : repoEnv.entrySet()) {
- if (!markerData.containsKey("ENV:" + value.getKey())) {
+ for (String key : keys) {
+ if (!markerData.containsKey("ENV:" + key)) {
return false;
}
- String markerValue = markerData.get("ENV:" + value.getKey());
- if (!Objects.equals(markerValue, value.getValue())) {
+ String markerValue = markerData.get("ENV:" + key);
+ if (!Objects.equals(markerValue, repoEnv.get(key))) {
return false;
}
}
@@ -439,8 +446,7 @@
}
@VisibleForTesting
- protected static PathFragment getTargetPath(String userDefinedPath, Path workspace)
- throws RepositoryFunctionException {
+ protected static PathFragment getTargetPath(String userDefinedPath, Path workspace) {
PathFragment pathFragment = PathFragment.create(userDefinedPath);
return workspace.getRelative(pathFragment).asFragment();
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionEnvironmentFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionEnvironmentFunction.java
index 25d12db..4edf0fb 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionEnvironmentFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionEnvironmentFunction.java
@@ -15,6 +15,7 @@
package com.google.devtools.build.lib.skyframe;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Interner;
import com.google.devtools.build.lib.bugreport.BugReport;
import com.google.devtools.build.lib.concurrent.BlazeInterners;
@@ -25,8 +26,6 @@
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.build.skyframe.SkyframeLookupResult;
-import java.util.Collections;
-import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.Nullable;
@@ -47,7 +46,7 @@
return env.getValue(ClientEnvironmentFunction.key(key));
}
- /** @return the SkyKey to invoke this function for the environment variable {@code variable}. */
+ /** Returns the SkyKey to invoke this function for the environment variable {@code variable}. */
public static Key key(String variable) {
return Key.create(variable);
}
@@ -78,8 +77,8 @@
* if and only if some dependencies from Skyframe still need to be resolved.
*/
@Nullable
- public static Map<String, String> getEnvironmentView(Environment env, Iterable<String> keys)
- throws InterruptedException {
+ public static ImmutableMap<String, String> getEnvironmentView(
+ Environment env, Iterable<String> keys) throws InterruptedException {
ImmutableList.Builder<SkyKey> skyframeKeysBuilder = ImmutableList.builder();
for (String key : keys) {
skyframeKeysBuilder.add(key(key));
@@ -89,8 +88,8 @@
if (env.valuesMissing()) {
return null;
}
- // To return the initial order and support null values, we use a LinkedHashMap.
- LinkedHashMap<String, String> result = new LinkedHashMap<>();
+
+ ImmutableMap.Builder<String, String> result = ImmutableMap.builder();
for (SkyKey key : skyframeKeys) {
ClientEnvironmentValue value = (ClientEnvironmentValue) values.get(key);
if (value == null) {
@@ -99,8 +98,10 @@
"ClientEnvironmentValue " + key + " was missing, this should never happen"));
return null;
}
- result.put(key.argument().toString(), value.getValue());
+ if (value.getValue() != null) {
+ result.put(key.argument().toString(), value.getValue());
+ }
}
- return Collections.unmodifiableMap(result);
+ return result.buildOrThrow();
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ClientEnvironmentFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ClientEnvironmentFunction.java
index 60bc5ad..510cfce 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ClientEnvironmentFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ClientEnvironmentFunction.java
@@ -32,6 +32,7 @@
return ClientEnvironmentFunction.Key.create(keyString);
}
+ /** The Skyframe key for the client environment function. */
@AutoCodec.VisibleForSerialization
@AutoCodec
static class Key extends AbstractSkyKey<String> {
@@ -61,7 +62,7 @@
@Nullable
@Override
- public SkyValue compute(SkyKey key, Environment env) throws InterruptedException {
+ public SkyValue compute(SkyKey key, Environment env) {
return new ClientEnvironmentValue(clientEnv.get().get((String) key.argument()));
}
}
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/FakeRepositoryModule.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/FakeRepositoryModule.java
index 8f9edc3..2cbf475 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/FakeRepositoryModule.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/FakeRepositoryModule.java
@@ -72,7 +72,6 @@
String doc,
StarlarkThread thread)
throws EvalException {
- List<AttributeInfo> attrInfos;
ImmutableMap.Builder<String, FakeDescriptor> attrsMapBuilder = ImmutableMap.builder();
if (attrs != null && attrs != Starlark.NONE) {
attrsMapBuilder.putAll(Dict.cast(attrs, String.class, FakeDescriptor.class, "attrs"));
@@ -80,7 +79,7 @@
attrsMapBuilder.put("name", IMPLICIT_NAME_ATTRIBUTE_DESCRIPTOR);
attrsMapBuilder.put("repo_mapping", IMPLICIT_REPO_MAPPING_ATTRIBUTE_DESCRIPTOR);
- attrInfos =
+ List<AttributeInfo> attrInfos =
attrsMapBuilder.build().entrySet().stream()
.filter(entry -> !entry.getKey().startsWith("_"))
.map(entry -> entry.getValue().asAttributeInfo(entry.getKey()))
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/StarlarkBazelModuleTest.java b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/StarlarkBazelModuleTest.java
index f20e7c0..6e0dd26 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/StarlarkBazelModuleTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/bzlmod/StarlarkBazelModuleTest.java
@@ -24,6 +24,7 @@
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.cmdline.Label;
@@ -61,6 +62,7 @@
.setName("maven")
.setDoc("")
.setLocation(Location.BUILTIN)
+ .setEnvVariables(ImmutableList.of())
.setDefinitionEnvironmentLabel(Label.parseAbsoluteUnchecked("//:rje.bzl"))
.setImplementation(() -> "maven");
}
diff --git a/src/test/py/bazel/bzlmod/bazel_lockfile_test.py b/src/test/py/bazel/bzlmod/bazel_lockfile_test.py
index f145c63..ce57008 100644
--- a/src/test/py/bazel/bzlmod/bazel_lockfile_test.py
+++ b/src/test/py/bazel/bzlmod/bazel_lockfile_test.py
@@ -282,23 +282,27 @@
' for dep in mod.tags.dep:',
' print("Name:", dep.name, ", Versions:", dep.versions)',
'',
- (
- '_dep = tag_class(attrs={"name": attr.string(), "versions":'
- ' attr.string_list()})'
- ),
+ '_dep = tag_class(attrs={"name": attr.string(), "versions":',
+ ' attr.string_list()})',
'lockfile_ext = module_extension(',
' implementation=_module_ext_impl,',
' tag_classes={"dep": _dep},',
+ ' environ=["GREEN_TREES", "NOT_SET"],',
')',
],
)
- _, _, stderr = self.RunBazel(['build', '@hello//:all'])
+ # Only set one env var, to make sure null variables don't crash
+ _, _, stderr = self.RunBazel(
+ ['build', '@hello//:all'], env_add={'GREEN_TREES': 'In the city'}
+ )
self.assertIn('Hello from the other side!', ''.join(stderr))
self.assertIn('Name: bmbm , Versions: ["v1", "v2"]', ''.join(stderr))
self.RunBazel(['shutdown'])
- _, _, stderr = self.RunBazel(['build', '@hello//:all'])
+ _, _, stderr = self.RunBazel(
+ ['build', '@hello//:all'], env_add={'GREEN_TREES': 'In the city'}
+ )
self.assertNotIn('Hello from the other side!', ''.join(stderr))
def testIsolatedModuleExtension(self):
@@ -403,13 +407,10 @@
' ctx.file("WORKSPACE")',
' ctx.file("BUILD", "filegroup(name=\\"lala\\")")',
'repo_rule = repository_rule(implementation = _repo_rule_impl)',
- 'def _module_ext_impl(ctx):',
+ 'def _mod_ext_impl(ctx):',
' print("Hello from the other side!")',
' repo_rule(name= "hello")',
- (
- 'lockfile_ext = module_extension(implementation ='
- ' _module_ext_impl)'
- ),
+ 'lockfile_ext = module_extension(implementation = _mod_ext_impl)',
],
)
_, _, stderr = self.RunBazel(['build', '@hello//:all'])
@@ -434,13 +435,10 @@
' ctx.file("WORKSPACE")',
' ctx.file("BUILD", "filegroup(name=\\"lala\\")")',
'repo_rule = repository_rule(implementation = _repo_rule_impl)',
- 'def _module_ext_impl(ctx):',
+ 'def _mod_ext_impl(ctx):',
' print("Hello from the other town!")',
' repo_rule(name= "hello")',
- (
- 'lockfile_ext = module_extension(implementation ='
- ' _module_ext_impl)'
- ),
+ 'lockfile_ext = module_extension(implementation = _mod_ext_impl)',
],
)
_, _, stderr = self.RunBazel(['build', '@hello//:all'])
@@ -468,13 +466,10 @@
' ctx.file("WORKSPACE")',
' ctx.file("BUILD", "filegroup(name=\\"lala\\")")',
'repo_rule = repository_rule(implementation = _repo_rule_impl)',
- 'def _module_ext_impl(ctx):',
+ 'def _mod_ext_impl(ctx):',
' print("Hello from the other side!")',
' repo_rule(name= "hello")',
- (
- 'lockfile_ext = module_extension(implementation ='
- ' _module_ext_impl)'
- ),
+ 'lockfile_ext = module_extension(implementation = _mod_ext_impl)',
],
)
_, _, stderr = self.RunBazel(['build', '@hello//:all'])
@@ -489,13 +484,10 @@
' ctx.file("WORKSPACE")',
' ctx.file("BUILD", "filegroup(name=\\"lalo\\")")',
'repo_rule = repository_rule(implementation = _repo_rule_impl)',
- 'def _module_ext_impl(ctx):',
+ 'def _mod_ext_impl(ctx):',
' print("Hello from the other town!")',
' repo_rule(name= "hello")',
- (
- 'lockfile_ext = module_extension(implementation ='
- ' _module_ext_impl)'
- ),
+ 'lockfile_ext = module_extension(implementation = _mod_ext_impl)',
],
)
@@ -554,14 +546,159 @@
def testNoAbsoluteRootModuleFilePath(self):
self.ScratchFile(
+ 'MODULE.bazel',
+ [
+ 'ext = use_extension("extension.bzl", "ext")',
+ 'ext.dep(generate = True)',
+ 'use_repo(ext, ext_hello = "hello")',
+ 'other_ext = use_extension("extension.bzl", "other_ext")',
+ 'other_ext.dep(generate = False)',
+ 'use_repo(other_ext, other_ext_hello = "hello")',
+ ],
+ )
+ self.ScratchFile('BUILD.bazel')
+ self.ScratchFile(
+ 'extension.bzl',
+ [
+ 'def _repo_rule_impl(ctx):',
+ ' ctx.file("WORKSPACE")',
+ ' ctx.file("BUILD", "filegroup(name=\'lala\')")',
+ '',
+ 'repo_rule = repository_rule(implementation=_repo_rule_impl)',
+ '',
+ 'def _module_ext_impl(ctx):',
+ ' for mod in ctx.modules:',
+ ' for dep in mod.tags.dep:',
+ ' if dep.generate:',
+ ' repo_rule(name="hello")',
+ '',
+ '_dep = tag_class(attrs={"generate": attr.bool()})',
+ 'ext = module_extension(',
+ ' implementation=_module_ext_impl,',
+ ' tag_classes={"dep": _dep},',
+ ')',
+ 'other_ext = module_extension(',
+ ' implementation=_module_ext_impl,',
+ ' tag_classes={"dep": _dep},',
+ ')',
+ ],
+ )
+
+ # Paths to module files in error message always use forward slashes as
+ # separators, even on Windows.
+ module_file_path = self.Path('MODULE.bazel').replace('\\', '/')
+
+ self.RunBazel(['build', '--nobuild', '@ext_hello//:all'])
+ with open(self.Path('MODULE.bazel.lock'), 'r') as f:
+ self.assertNotIn(module_file_path, f.read())
+
+ self.RunBazel(['shutdown'])
+ exit_code, _, stderr = self.RunBazel(
+ ['build', '--nobuild', '@other_ext_hello//:all'], allow_failure=True
+ )
+ self.AssertNotExitCode(exit_code, 0, stderr)
+ self.assertIn(
+ (
+ 'ERROR: module extension "other_ext" from "//:extension.bzl" does '
+ 'not generate repository "hello", yet it is imported as '
+ '"other_ext_hello" in the usage at '
+ + module_file_path
+ + ':4:26'
+ ),
+ stderr,
+ )
+
+ def testModuleExtensionEnvVariable(self):
+ self.ScratchFile(
'MODULE.bazel',
[
- 'ext = use_extension("extension.bzl", "ext")',
- 'ext.dep(generate = True)',
- 'use_repo(ext, ext_hello = "hello")',
- 'other_ext = use_extension("extension.bzl", "other_ext")',
- 'other_ext.dep(generate = False)',
- 'use_repo(other_ext, other_ext_hello = "hello")',
+ 'lockfile_ext = use_extension("extension.bzl", "lockfile_ext")',
+ 'use_repo(lockfile_ext, "hello")',
+ ],
+ )
+ self.ScratchFile('BUILD.bazel')
+ self.ScratchFile(
+ 'extension.bzl',
+ [
+ 'def _repo_rule_impl(ctx):',
+ ' ctx.file("WORKSPACE")',
+ ' ctx.file("BUILD", "filegroup(name=\\"lala\\")")',
+ 'repo_rule = repository_rule(implementation = _repo_rule_impl)',
+ 'def _module_ext_impl(ctx):',
+ ' print("Hello from the other side!")',
+ ' repo_rule(name= "hello")',
+ 'lockfile_ext = module_extension(',
+ ' implementation = _module_ext_impl,',
+ ' environ = ["SET_ME"]',
+ ')',
+ ],
+ )
+ _, _, stderr = self.RunBazel(
+ ['build', '@hello//:all'], env_add={'SET_ME': 'High in sky'}
+ )
+ self.assertIn('Hello from the other side!', ''.join(stderr))
+ # Run with same value, no evaluated
+ _, _, stderr = self.RunBazel(
+ ['build', '@hello//:all'], env_add={'SET_ME': 'High in sky'}
+ )
+ self.assertNotIn('Hello from the other side!', ''.join(stderr))
+ # Run with different value, will be re-evaluated
+ _, _, stderr = self.RunBazel(
+ ['build', '@hello//:all'], env_add={'SET_ME': 'Down to earth'}
+ )
+ self.assertIn('Hello from the other side!', ''.join(stderr))
+
+ def testChangeEnvVariableInErrorMode(self):
+ # If environ is set up in module extension, it should be re-evaluated if its
+ # value changed
+ self.ScratchFile(
+ 'MODULE.bazel',
+ [
+ 'lockfile_ext = use_extension("extension.bzl", "lockfile_ext")',
+ 'use_repo(lockfile_ext, "hello")',
+ ],
+ )
+ self.ScratchFile('BUILD.bazel')
+ self.ScratchFile(
+ 'extension.bzl',
+ [
+ 'def _repo_rule_impl(ctx):',
+ ' ctx.file("WORKSPACE")',
+ ' ctx.file("BUILD", "filegroup(name=\\"lala\\")")',
+ 'repo_rule = repository_rule(implementation = _repo_rule_impl)',
+ 'def _module_ext_impl(ctx):',
+ ' print("Hello from the other side!")',
+ ' repo_rule(name= "hello")',
+ 'lockfile_ext = module_extension(',
+ ' implementation = _module_ext_impl,',
+ ' environ = ["SET_ME"]',
+ ')',
+ ],
+ )
+ self.RunBazel(['build', '@hello//:all'], env_add={'SET_ME': 'High in sky'})
+ exit_code, _, stderr = self.RunBazel(
+ ['build', '--lockfile_mode=error', '@hello//:all'],
+ env_add={'SET_ME': 'Down to earth'},
+ allow_failure=True,
+ )
+ self.AssertExitCode(exit_code, 48, stderr)
+ self.assertIn(
+ (
+ 'ERROR: Lock file is no longer up-to-date because: The environment'
+ ' variables the extension'
+ " 'ModuleExtensionId{bzlFileLabel=//:extension.bzl,"
+ " extensionName=lockfile_ext, isolationKey=Optional.empty}' depends"
+ ' on (or their values) have changed'
+ ),
+ stderr,
+ )
+
+ def testModuleExtensionWithFile(self):
+ self.ScratchFile(
+ 'MODULE.bazel',
+ [
+ 'lockfile_ext = use_extension("extension.bzl", "lockfile_ext")',
+ 'use_repo(lockfile_ext, "hello")',
],
)
self.ScratchFile('BUILD.bazel')
@@ -575,46 +712,28 @@
'repo_rule = repository_rule(implementation=_repo_rule_impl)',
'',
'def _module_ext_impl(ctx):',
- ' for mod in ctx.modules:',
- ' for dep in mod.tags.dep:',
- ' if dep.generate:',
- ' repo_rule(name="hello")',
+ ' print(ctx.read(Label("//:hello.txt")))',
+ ' repo_rule(name="hello")',
'',
- '_dep = tag_class(attrs={"generate": attr.bool()})',
- 'ext = module_extension(',
- ' implementation=_module_ext_impl,',
- ' tag_classes={"dep": _dep},',
- ')',
- 'other_ext = module_extension(',
- ' implementation=_module_ext_impl,',
- ' tag_classes={"dep": _dep},',
+ 'lockfile_ext = module_extension(',
+ ' implementation=_module_ext_impl',
')',
],
)
- # Paths to module files in error message always use forward slashes as
- # separators, even on Windows.
- module_file_path = self.Path('MODULE.bazel').replace('\\', '/')
+ self.ScratchFile('hello.txt', ['I will not stay the same.'])
+ _, _, stderr = self.RunBazel(['build', '@hello//:all'])
+ self.assertIn('I will not stay the same.', ''.join(stderr))
- self.RunBazel(['build', '--nobuild', '@ext_hello//:all'])
- with open(self.Path('MODULE.bazel.lock'), 'r') as f:
- self.assertNotIn(module_file_path, f.read())
-
+ # Shutdown bazel to empty cache and run with no changes
self.RunBazel(['shutdown'])
- exit_code, _, stderr = self.RunBazel(
- ['build', '--nobuild', '@other_ext_hello//:all'], allow_failure=True
- )
- self.AssertNotExitCode(exit_code, 0, stderr)
- self.assertIn(
- (
- 'ERROR: module extension "other_ext" from "//:extension.bzl" does '
- 'not generate repository "hello", yet it is imported as '
- '"other_ext_hello" in the usage at '
- + module_file_path
- + ':4:26'
- ),
- stderr,
- )
+ _, _, stderr = self.RunBazel(['build', '@hello//:all'])
+ self.assertNotIn('I will not stay the same.', ''.join(stderr))
+
+ # Update file and rerun
+ self.ScratchFile('hello.txt', ['I have changed now!'])
+ _, _, stderr = self.RunBazel(['build', '@hello//:all'])
+ self.assertIn('I have changed now!', ''.join(stderr))
if __name__ == '__main__':