Skip to content

Commit 13908f9

Browse files
fmeumWyverald
authored andcommitted
Track getenv calls in module extensions
Fixes #22404 RELNOTES: Changes to environment variables read via `getenv` now correctly invalidate module extensions. Closes #22498. PiperOrigin-RevId: 637058337 Change-Id: Id4aaa4155a728452472eedae4a59c8d29456e512
1 parent faeb2d7 commit 13908f9

12 files changed

+240
-54
lines changed

MODULE.bazel.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
@GenerateTypeAdapter
3636
public abstract class BazelLockFileValue implements SkyValue, Postable {
3737

38-
public static final int LOCK_FILE_VERSION = 10;
38+
public static final int LOCK_FILE_VERSION = 11;
3939

4040
@SerializationConstant public static final SkyKey KEY = () -> SkyFunctions.BAZEL_LOCK_FILE;
4141

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

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,22 +232,34 @@ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
232232
if (elementTypeAdapter == null) {
233233
return null;
234234
}
235-
return (TypeAdapter<T>) new OptionalTypeAdapter<>(elementTypeAdapter);
235+
// Explicit nulls for Optional.empty are required for env variable tracking, but are too
236+
// noisy and unnecessary for other types.
237+
return (TypeAdapter<T>)
238+
new OptionalTypeAdapter<>(
239+
elementTypeAdapter, /* serializeNulls= */ elementType.equals(String.class));
236240
}
237241
};
238242

239243
private static final class OptionalTypeAdapter<T> extends TypeAdapter<Optional<T>> {
240244
private final TypeAdapter<T> elementTypeAdapter;
245+
private final boolean serializeNulls;
241246

242-
public OptionalTypeAdapter(TypeAdapter<T> elementTypeAdapter) {
247+
public OptionalTypeAdapter(TypeAdapter<T> elementTypeAdapter, boolean serializeNulls) {
243248
this.elementTypeAdapter = elementTypeAdapter;
249+
this.serializeNulls = serializeNulls;
244250
}
245251

246252
@Override
247253
public void write(JsonWriter jsonWriter, Optional<T> t) throws IOException {
248254
Preconditions.checkNotNull(t);
249255
if (t.isEmpty()) {
250-
jsonWriter.nullValue();
256+
boolean oldSerializeNulls = jsonWriter.getSerializeNulls();
257+
jsonWriter.setSerializeNulls(serializeNulls);
258+
try {
259+
jsonWriter.nullValue();
260+
} finally {
261+
jsonWriter.setSerializeNulls(oldSerializeNulls);
262+
}
251263
} else {
252264
elementTypeAdapter.write(jsonWriter, t.get());
253265
}
@@ -358,6 +370,22 @@ public RepoRecordedInput.Dirents read(JsonReader jsonReader) throws IOException
358370
}
359371
};
360372

373+
private static final TypeAdapter<RepoRecordedInput.EnvVar>
374+
REPO_RECORDED_INPUT_ENV_VAR_TYPE_ADAPTER =
375+
new TypeAdapter<>() {
376+
@Override
377+
public void write(JsonWriter jsonWriter, RepoRecordedInput.EnvVar value)
378+
throws IOException {
379+
jsonWriter.value(value.toStringInternal());
380+
}
381+
382+
@Override
383+
public RepoRecordedInput.EnvVar read(JsonReader jsonReader) throws IOException {
384+
return (RepoRecordedInput.EnvVar)
385+
RepoRecordedInput.EnvVar.PARSER.parse(jsonReader.nextString());
386+
}
387+
};
388+
361389
// This can't reuse the existing type adapter factory for Optional as we need to explicitly
362390
// serialize null values but don't want to rely on GSON's serializeNulls.
363391
private static final class OptionalChecksumTypeAdapterFactory implements TypeAdapterFactory {
@@ -441,7 +469,9 @@ private static GsonBuilder newGsonBuilder() {
441469
.registerTypeAdapter(byte[].class, BYTE_ARRAY_TYPE_ADAPTER)
442470
.registerTypeAdapter(RepoRecordedInput.File.class, REPO_RECORDED_INPUT_FILE_TYPE_ADAPTER)
443471
.registerTypeAdapter(
444-
RepoRecordedInput.Dirents.class, REPO_RECORDED_INPUT_DIRENTS_TYPE_ADAPTER);
472+
RepoRecordedInput.Dirents.class, REPO_RECORDED_INPUT_DIRENTS_TYPE_ADAPTER)
473+
.registerTypeAdapter(
474+
RepoRecordedInput.EnvVar.class, REPO_RECORDED_INPUT_ENV_VAR_TYPE_ADAPTER);
445475
}
446476

447477
private GsonTypeAdapterUtil() {}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public static Builder builder() {
4848

4949
public abstract ImmutableMap<RepoRecordedInput.Dirents, String> getRecordedDirentsInputs();
5050

51-
public abstract ImmutableMap<String, String> getEnvVariables();
51+
public abstract ImmutableMap<RepoRecordedInput.EnvVar, Optional<String>> getEnvVariables();
5252

5353
public abstract ImmutableMap<String, RepoSpec> getGeneratedRepoSpecs();
5454

@@ -78,7 +78,8 @@ public abstract Builder setRecordedFileInputs(
7878
public abstract Builder setRecordedDirentsInputs(
7979
ImmutableMap<RepoRecordedInput.Dirents, String> value);
8080

81-
public abstract Builder setEnvVariables(ImmutableMap<String, String> value);
81+
public abstract Builder setEnvVariables(
82+
ImmutableMap<RepoRecordedInput.EnvVar, Optional<String>> value);
8283

8384
public abstract Builder setGeneratedRepoSpecs(ImmutableMap<String, RepoSpec> value);
8485

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

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.google.common.collect.ImmutableList;
2626
import com.google.common.collect.ImmutableMap;
2727
import com.google.common.collect.ImmutableSet;
28+
import com.google.common.collect.ImmutableSortedMap;
2829
import com.google.common.collect.ImmutableTable;
2930
import com.google.common.collect.Iterables;
3031
import com.google.common.collect.Maps;
@@ -230,6 +231,15 @@ public SkyValue compute(SkyKey skyKey, Environment env)
230231
// result is taken from the lockfile, we can already populate the lockfile info. This is
231232
// necessary to prevent the extension from rerunning when only the imports change.
232233
if (lockfileMode == LockfileMode.UPDATE || lockfileMode == LockfileMode.REFRESH) {
234+
var envVariables =
235+
ImmutableMap.<RepoRecordedInput.EnvVar, Optional<String>>builder()
236+
// The environment variable dependencies statically declared via the 'environ'
237+
// attribute.
238+
.putAll(RepoRecordedInput.EnvVar.wrap(extension.getStaticEnvVars()))
239+
// The environment variable dependencies dynamically declared via the 'getenv' method.
240+
.putAll(moduleExtensionResult.getRecordedEnvVarInputs())
241+
.buildKeepingLast();
242+
233243
lockFileInfo =
234244
Optional.of(
235245
new LockFileModuleExtension.WithFactors(
@@ -241,7 +251,7 @@ public SkyValue compute(SkyKey skyKey, Environment env)
241251
GsonTypeAdapterUtil.SINGLE_EXTENSION_USAGES_VALUE_GSON, usagesValue))
242252
.setRecordedFileInputs(moduleExtensionResult.getRecordedFileInputs())
243253
.setRecordedDirentsInputs(moduleExtensionResult.getRecordedDirentsInputs())
244-
.setEnvVariables(extension.getEnvVars())
254+
.setEnvVariables(ImmutableSortedMap.copyOf(envVariables))
245255
.setGeneratedRepoSpecs(generatedRepoSpecs)
246256
.setModuleExtensionMetadata(moduleExtensionMetadata)
247257
.setRecordedRepoMappingEntries(
@@ -284,7 +294,11 @@ private SingleExtensionValue tryGettingValueFromLockFile(
284294
+ extensionId
285295
+ "' or one of its transitive .bzl files has changed");
286296
}
287-
if (!extension.getEnvVars().equals(lockedExtension.getEnvVariables())) {
297+
if (didRecordedInputsChange(
298+
env,
299+
directories,
300+
// didRecordedInputsChange expects possibly null String values.
301+
Maps.transformValues(lockedExtension.getEnvVariables(), v -> v.orElse(null)))) {
288302
diffRecorder.record(
289303
"The environment variables the extension '"
290304
+ extensionId
@@ -415,7 +429,7 @@ private static boolean didRepoMappingsChange(
415429
private static boolean didRecordedInputsChange(
416430
Environment env,
417431
BlazeDirectories directories,
418-
ImmutableMap<? extends RepoRecordedInput, String> recordedInputs)
432+
Map<? extends RepoRecordedInput, String> recordedInputs)
419433
throws InterruptedException, NeedsSkyframeRestartException {
420434
boolean upToDate = RepoRecordedInput.areAllValuesUpToDate(env, directories, recordedInputs);
421435
if (env.valuesMissing()) {
@@ -522,15 +536,15 @@ private BzlLoadValue loadBzlFile(
522536
* <p>The general idiom is to "load" such a {@link RunnableExtension} object by getting as much
523537
* information about it as needed to determine whether it can be reused from the lockfile (hence
524538
* methods such as {@link #getEvalFactors()}, {@link #getBzlTransitiveDigest()}, {@link
525-
* #getEnvVars()}). Then the {@link #run} method can be called if it's determined that we can't
526-
* reuse the cached results in the lockfile and have to re-run this extension.
539+
* #getStaticEnvVars()}). Then the {@link #run} method can be called if it's determined that we
540+
* can't reuse the cached results in the lockfile and have to re-run this extension.
527541
*/
528542
private interface RunnableExtension {
529543
ModuleExtensionEvalFactors getEvalFactors();
530544

531545
byte[] getBzlTransitiveDigest();
532546

533-
ImmutableMap<String, String> getEnvVars();
547+
ImmutableMap<String, Optional<String>> getStaticEnvVars();
534548

535549
@Nullable
536550
RunModuleExtensionResult run(
@@ -681,7 +695,7 @@ public byte[] getBzlTransitiveDigest() {
681695
}
682696

683697
@Override
684-
public ImmutableMap<String, String> getEnvVars() {
698+
public ImmutableMap<String, Optional<String>> getStaticEnvVars() {
685699
return ImmutableMap.of();
686700
}
687701

@@ -773,6 +787,7 @@ public RunModuleExtensionResult run(
773787
generatedRepoSpecs.put(name, repoSpec);
774788
}
775789
return RunModuleExtensionResult.create(
790+
ImmutableMap.of(),
776791
ImmutableMap.of(),
777792
ImmutableMap.of(),
778793
generatedRepoSpecs.buildOrThrow(),
@@ -820,7 +835,7 @@ private RegularRunnableExtension loadRegularRunnableExtension(
820835
}
821836

822837
ModuleExtension extension = (ModuleExtension) exported;
823-
ImmutableMap<String, String> envVars =
838+
ImmutableMap<String, Optional<String>> envVars =
824839
RepositoryFunction.getEnvVarValues(env, ImmutableSet.copyOf(extension.getEnvVariables()));
825840
if (envVars == null) {
826841
return null;
@@ -831,15 +846,15 @@ private RegularRunnableExtension loadRegularRunnableExtension(
831846
private final class RegularRunnableExtension implements RunnableExtension {
832847
private final BzlLoadValue bzlLoadValue;
833848
private final ModuleExtension extension;
834-
private final ImmutableMap<String, String> envVars;
849+
private final ImmutableMap<String, Optional<String>> staticEnvVars;
835850

836851
RegularRunnableExtension(
837852
BzlLoadValue bzlLoadValue,
838853
ModuleExtension extension,
839-
ImmutableMap<String, String> envVars) {
854+
ImmutableMap<String, Optional<String>> staticEnvVars) {
840855
this.bzlLoadValue = bzlLoadValue;
841856
this.extension = extension;
842-
this.envVars = envVars;
857+
this.staticEnvVars = staticEnvVars;
843858
}
844859

845860
@Override
@@ -850,8 +865,8 @@ public ModuleExtensionEvalFactors getEvalFactors() {
850865
}
851866

852867
@Override
853-
public ImmutableMap<String, String> getEnvVars() {
854-
return envVars;
868+
public ImmutableMap<String, Optional<String>> getStaticEnvVars() {
869+
return staticEnvVars;
855870
}
856871

857872
@Override
@@ -951,6 +966,7 @@ public RunModuleExtensionResult run(
951966
return RunModuleExtensionResult.create(
952967
moduleContext.getRecordedFileInputs(),
953968
moduleContext.getRecordedDirentsInputs(),
969+
moduleContext.getRecordedEnvVarInputs(),
954970
threadContext.getGeneratedRepoSpecs(),
955971
moduleExtensionMetadata,
956972
repoMappingRecorder.recordedEntries());
@@ -1015,6 +1031,8 @@ abstract static class RunModuleExtensionResult {
10151031

10161032
abstract ImmutableMap<RepoRecordedInput.Dirents, String> getRecordedDirentsInputs();
10171033

1034+
abstract ImmutableMap<RepoRecordedInput.EnvVar, Optional<String>> getRecordedEnvVarInputs();
1035+
10181036
abstract ImmutableMap<String, RepoSpec> getGeneratedRepoSpecs();
10191037

10201038
abstract Optional<ModuleExtensionMetadata> getModuleExtensionMetadata();
@@ -1024,12 +1042,14 @@ abstract static class RunModuleExtensionResult {
10241042
static RunModuleExtensionResult create(
10251043
ImmutableMap<RepoRecordedInput.File, String> recordedFileInputs,
10261044
ImmutableMap<RepoRecordedInput.Dirents, String> recordedDirentsInputs,
1045+
ImmutableMap<RepoRecordedInput.EnvVar, Optional<String>> recordedEnvVarInputs,
10271046
ImmutableMap<String, RepoSpec> generatedRepoSpecs,
10281047
Optional<ModuleExtensionMetadata> moduleExtensionMetadata,
10291048
ImmutableTable<RepositoryName, String, RepositoryName> recordedRepoMappingEntries) {
10301049
return new AutoValue_SingleExtensionEvalFunction_RunModuleExtensionResult(
10311050
recordedFileInputs,
10321051
recordedDirentsInputs,
1052+
recordedEnvVarInputs,
10331053
generatedRepoSpecs,
10341054
moduleExtensionMetadata,
10351055
recordedRepoMappingEntries);

src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkBaseExternalContext.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import com.google.common.base.Strings;
2323
import com.google.common.collect.ImmutableList;
2424
import com.google.common.collect.ImmutableMap;
25-
import com.google.common.collect.ImmutableSet;
2625
import com.google.common.collect.ImmutableSortedMap;
2726
import com.google.common.collect.Maps;
2827
import com.google.common.util.concurrent.Futures;
@@ -214,9 +213,12 @@ public ImmutableMap<Dirents, String> getRecordedDirentsInputs() {
214213
return ImmutableMap.copyOf(recordedDirentsInputs);
215214
}
216215

217-
/** Returns set of environment variable keys encountered so far. */
218-
public ImmutableSet<String> getAccumulatedEnvKeys() {
219-
return ImmutableSet.copyOf(accumulatedEnvKeys);
216+
public ImmutableMap<RepoRecordedInput.EnvVar, Optional<String>> getRecordedEnvVarInputs()
217+
throws InterruptedException {
218+
// getEnvVarValues doesn't return null since the Skyframe dependencies have already been
219+
// established by getenv calls.
220+
return RepoRecordedInput.EnvVar.wrap(
221+
ImmutableSortedMap.copyOf(RepositoryFunction.getEnvVarValues(env, accumulatedEnvKeys)));
220222
}
221223

222224
protected void checkInOutputDirectory(String operation, StarlarkPath path)

src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryFunction.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.common.collect.ImmutableList;
2121
import com.google.common.collect.ImmutableMap;
2222
import com.google.common.collect.ImmutableSet;
23+
import com.google.common.collect.Maps;
2324
import com.google.common.collect.Table;
2425
import com.google.devtools.build.lib.analysis.BlazeDirectories;
2526
import com.google.devtools.build.lib.analysis.RuleDefinition;
@@ -362,16 +363,14 @@ private RepositoryDirectoryValue.Builder fetchInternal(
362363
env.getListener().handle(Event.debug(defInfo));
363364
}
364365

365-
// Modify marker data to include the files/dirents used by the rule's implementation function.
366+
// Modify marker data to include the files/dirents/env vars used by the rule's implementation
367+
// function.
366368
recordedInputValues.putAll(starlarkRepositoryContext.getRecordedFileInputs());
367369
recordedInputValues.putAll(starlarkRepositoryContext.getRecordedDirentsInputs());
368370
recordedInputValues.putAll(starlarkRepositoryContext.getRecordedDirTreeInputs());
369-
370-
// Ditto for environment variables accessed via `getenv`.
371-
for (String envKey : starlarkRepositoryContext.getAccumulatedEnvKeys()) {
372-
recordedInputValues.put(
373-
new RepoRecordedInput.EnvVar(envKey), clientEnvironment.get(envKey));
374-
}
371+
recordedInputValues.putAll(
372+
Maps.transformValues(
373+
starlarkRepositoryContext.getRecordedEnvVarInputs(), v -> v.orElse(null)));
375374

376375
for (Table.Cell<RepositoryName, String, RepositoryName> repoMappings :
377376
repoMappingRecorder.recordedEntries().cellSet()) {

src/main/java/com/google/devtools/build/lib/rules/repository/RepoRecordedInput.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
package com.google.devtools.build.lib.rules.repository;
1616

1717
import static com.google.common.collect.ImmutableList.toImmutableList;
18+
import static com.google.common.collect.ImmutableMap.toImmutableMap;
1819
import static com.google.common.collect.ImmutableSet.toImmutableSet;
1920

2021
import com.google.auto.value.AutoValue;
2122
import com.google.common.base.Preconditions;
2223
import com.google.common.base.Splitter;
24+
import com.google.common.collect.ImmutableMap;
2325
import com.google.common.io.BaseEncoding;
2426
import com.google.devtools.build.lib.actions.FileValue;
2527
import com.google.devtools.build.lib.analysis.BlazeDirectories;
@@ -494,7 +496,7 @@ public boolean isUpToDate(
494496

495497
/** Represents an environment variable accessed during the repo fetch. */
496498
public static final class EnvVar extends RepoRecordedInput {
497-
static final Parser PARSER =
499+
public static final Parser PARSER =
498500
new Parser() {
499501
@Override
500502
public String getPrefix() {
@@ -509,7 +511,13 @@ public RepoRecordedInput parse(String s) {
509511

510512
final String name;
511513

512-
public EnvVar(String name) {
514+
public static ImmutableMap<EnvVar, Optional<String>> wrap(
515+
Map<String, Optional<String>> envVars) {
516+
return envVars.entrySet().stream()
517+
.collect(toImmutableMap(e -> new EnvVar(e.getKey()), Map.Entry::getValue));
518+
}
519+
520+
private EnvVar(String name) {
513521
this.name = name;
514522
}
515523

0 commit comments

Comments
 (0)