Properly setup package cache before doing starlark options parsing.
Consolidate calls to CommandEnvironment#setupPackageCache to one place pre-starlark options parsing and pre-command execution. Both starlark options parsing and CommandEnviroment#setupPackageCache happen only for commands that build (according to CommandAnnotation#build) (with some exceptions).
PiperOrigin-RevId: 252751079
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
index c00274d..da70c87 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
@@ -110,10 +110,6 @@
try (SilentCloseable c = Profiler.instance().profile("createBuildOptions")) {
buildOptions = runtime.createBuildOptions(request);
}
- // Sync the package manager before sending the BuildStartingEvent in runLoadingPhase()
- try (SilentCloseable c = Profiler.instance().profile("setupPackageCache")) {
- env.setupPackageCache(request);
- }
ExecutionTool executionTool = null;
boolean catastrophe = false;
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
index b0dd9a6..ae20413 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
@@ -464,6 +464,36 @@
}
}
+ // {@link CleanCommand} is annotated with {@code builds = true}
+ // to have access to relevant build options but don't actually do building.
+ // {@link InfoCommand} is annotated with {@code builds = true} but only conditionally
+ // does this step based on some complicated logic.
+ if (commandAnnotation.builds()
+ && !commandAnnotation.name().equals("clean")
+ && !commandAnnotation.name().equals("info")) {
+ try {
+ env.setupPackageCache(options);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ env.getReporter()
+ .handle(Event.error("command interrupted while setting up package cache"));
+ earlyExitCode = ExitCode.INTERRUPTED;
+ } catch (AbruptExitException e) {
+ env.getReporter().handle(Event.error(e.getMessage()));
+ earlyExitCode = e.getExitCode();
+ }
+ if (!earlyExitCode.equals(ExitCode.SUCCESS)) {
+ return replayEarlyExitEvents(
+ outErr,
+ optionHandler,
+ storedEventHandler,
+ env,
+ earlyExitCode,
+ new NoBuildEvent(
+ commandName, firstContactTime, false, true, env.getCommandId().toString()));
+ }
+ }
+
// Parse starlark options.
earlyExitCode = optionHandler.parseStarlarkOptions(env, storedEventHandler);
if (!earlyExitCode.equals(ExitCode.SUCCESS)) {
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeOptionHandler.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeOptionHandler.java
index 7d28683..9944a75 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeOptionHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeOptionHandler.java
@@ -244,11 +244,11 @@
return ExitCode.SUCCESS;
}
try {
- StarlarkOptionsParser.newStarlarkOptionsParser(env, optionsParser, runtime)
+ StarlarkOptionsParser.newStarlarkOptionsParser(env, optionsParser)
.parse(commandAnnotation, eventHandler);
} catch (OptionsParsingException e) {
- eventHandler.handle(Event.error(e.getMessage()));
- return ExitCode.COMMAND_LINE_ERROR;
+ env.getReporter().handle(Event.error(e.getMessage()));
+ return ExitCode.PARSING_FAILURE;
}
return ExitCode.SUCCESS;
}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java b/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
index eaf0153..668b333 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
@@ -89,6 +89,7 @@
private OutputService outputService;
private Path workingDirectory;
private String workspaceName;
+ private boolean haveSetupPackageCache = false;
private AtomicReference<AbruptExitException> pendingException = new AtomicReference<>();
@@ -571,6 +572,15 @@
*/
public void setupPackageCache(OptionsProvider options)
throws InterruptedException, AbruptExitException {
+ // We want to ensure that we're never calling #setupPackageCache twice in the same build because
+ // it does the very expensive work of diffing the cache between incremental builds.
+ // {@link SequencedSkyframeExecutor#handleDiffs} is the particular method we don't want to be
+ // calling twice. We could feasibly factor it out of this call.
+ if (this.haveSetupPackageCache) {
+ throw new IllegalStateException(
+ "We should never call this method more than once over the course of a single command");
+ }
+ this.haveSetupPackageCache = true;
getSkyframeExecutor()
.sync(
reporter,
@@ -583,19 +593,6 @@
options);
}
- public void syncPackageLoading(
- PackageCacheOptions packageCacheOptions, StarlarkSemanticsOptions starlarkSemanticsOptions)
- throws AbruptExitException {
- getSkyframeExecutor()
- .syncPackageLoading(
- packageCacheOptions,
- packageLocator,
- starlarkSemanticsOptions,
- getCommandId(),
- clientEnv,
- timestampGranularityMonitor);
- }
-
public void recordLastExecutionTime() {
workspace.recordLastExecutionTime(getCommandStartTime());
}
@@ -687,6 +684,9 @@
eventBus.post(new CommandStartEvent(
command.name(), getCommandId(), getClientEnv(), workingDirectory, getDirectories(),
waitTimeInMs + commonOptions.waitTime));
+
+ // Modules that are subscribed to CommandStartEvents may create pending exceptions.
+ throwPendingException();
}
/** Returns the name of the file system we are writing output to. */
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/StarlarkOptionsParser.java b/src/main/java/com/google/devtools/build/lib/runtime/StarlarkOptionsParser.java
index 471419d..bba0827 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/StarlarkOptionsParser.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/StarlarkOptionsParser.java
@@ -28,14 +28,11 @@
import com.google.devtools.build.lib.events.Reporter;
import com.google.devtools.build.lib.packages.BuildSetting;
import com.google.devtools.build.lib.packages.Rule;
-import com.google.devtools.build.lib.packages.StarlarkSemanticsOptions;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.pkgcache.LoadingOptions;
-import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
import com.google.devtools.build.lib.skyframe.TargetPatternPhaseValue;
import com.google.devtools.build.lib.syntax.Type;
-import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.common.options.Converter;
@@ -70,15 +67,7 @@
}
static StarlarkOptionsParser newStarlarkOptionsParser(
- CommandEnvironment env, OptionsParser optionsParser, BlazeRuntime runtime)
- throws OptionsParsingException {
- try {
- env.syncPackageLoading(
- optionsParser.getOptions(PackageCacheOptions.class),
- optionsParser.getOptions(StarlarkSemanticsOptions.class));
- } catch (AbruptExitException e) {
- throw new OptionsParsingException(e.getMessage());
- }
+ CommandEnvironment env, OptionsParser optionsParser) {
return new StarlarkOptionsParser(
env.getSkyframeExecutor(),
env.getRelativeWorkingDirectory(),
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/BuildCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/BuildCommand.java
index 96f0369..78d3eb2 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/BuildCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/BuildCommand.java
@@ -70,6 +70,7 @@
BlazeRuntime runtime = env.getRuntime();
List<String> targets;
try (SilentCloseable closeable = Profiler.instance().profile("ProjectFileSupport.getTargets")) {
+ // only takes {@code options} to get options.getResidue()
targets = ProjectFileSupport.getTargets(runtime.getProjectFileProvider(), options);
}
if (targets.isEmpty()) {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index 7238381..acac27a 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -2633,8 +2633,8 @@
TimestampGranularityMonitor tsgm,
OptionsProvider options)
throws InterruptedException, AbruptExitException {
- getActionEnvFromOptions(options);
- setRepoEnv(options);
+ getActionEnvFromOptions(options.getOptions(CoreOptions.class));
+ setRepoEnv(options.getOptions(CoreOptions.class));
RemoteOptions remoteOptions = options.getOptions(RemoteOptions.class);
setRemoteOutputsMode(
remoteOptions != null
@@ -2679,10 +2679,9 @@
invalidateTransientErrors();
}
- private void getActionEnvFromOptions(OptionsProvider options) {
+ private void getActionEnvFromOptions(CoreOptions opt) {
// ImmutableMap does not support null values, so use a LinkedHashMap instead.
LinkedHashMap<String, String> actionEnvironment = new LinkedHashMap<>();
- CoreOptions opt = options.getOptions(CoreOptions.class);
if (opt != null) {
for (Map.Entry<String, String> v : opt.actionEnvironment) {
actionEnvironment.put(v.getKey(), v.getValue());
@@ -2696,8 +2695,7 @@
PrecomputedValue.ACTION_ENV.set(injectable(), actionEnv);
}
- private void setRepoEnv(OptionsProvider options) {
- CoreOptions opt = options.getOptions(CoreOptions.class);
+ private void setRepoEnv(CoreOptions opt) {
LinkedHashMap<String, String> repoEnv = new LinkedHashMap<>();
if (opt != null) {
for (Map.Entry<String, String> v : opt.repositoryEnvironment) {
diff --git a/src/test/java/com/google/devtools/build/lib/buildtool/BUILD b/src/test/java/com/google/devtools/build/lib/buildtool/BUILD
index b008e6e..684581f 100644
--- a/src/test/java/com/google/devtools/build/lib/buildtool/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/buildtool/BUILD
@@ -34,6 +34,7 @@
"//src/main/java/com/google/devtools/build/lib/exec/local:options",
"//src/main/java/com/google/devtools/build/lib/includescanning",
"//src/main/java/com/google/devtools/build/lib/network:noop_connectivity",
+ "//src/main/java/com/google/devtools/build/lib/profiler",
"//src/main/java/com/google/devtools/build/lib/sandbox",
"//src/main/java/com/google/devtools/build/lib/shell",
"//src/main/java/com/google/devtools/build/lib/standalone",
diff --git a/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java b/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java
index 4ea6a29..230d6ae 100644
--- a/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java
+++ b/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java
@@ -36,6 +36,8 @@
import com.google.devtools.build.lib.packages.StarlarkSemanticsOptions;
import com.google.devtools.build.lib.pkgcache.LoadingOptions;
import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
+import com.google.devtools.build.lib.profiler.Profiler;
+import com.google.devtools.build.lib.profiler.SilentCloseable;
import com.google.devtools.build.lib.runtime.BlazeCommand;
import com.google.devtools.build.lib.runtime.BlazeCommandEventHandler;
import com.google.devtools.build.lib.runtime.BlazeCommandResult;
@@ -335,6 +337,9 @@
}
try {
+ try (SilentCloseable c = Profiler.instance().profile("setupPackageCache")) {
+ env.setupPackageCache(lastRequest);
+ }
buildTool.buildTargets(lastRequest, lastResult, null);
success = true;
} finally {
diff --git a/src/test/shell/integration/discard_graph_edges_test.sh b/src/test/shell/integration/discard_graph_edges_test.sh
index 32bd972..aab82d9 100755
--- a/src/test/shell/integration/discard_graph_edges_test.sh
+++ b/src/test/shell/integration/discard_graph_edges_test.sh
@@ -403,9 +403,10 @@
genrule(name = "foo", cmd = "touch $@", outs = ["foo.out"])
EOF
+ readonly local server_pid="$(bazel info server_pid 2> /dev/null)"
bazel build $BUILD_FLAGS //foo:foo \
>& "$TEST_log" || fail "Expected success"
- "$jmaptool" -histo:live "$(bazel info server_pid)" > histo.txt
+ "$jmaptool" -histo:live $server_pid > histo.txt
genrule_action_count="$(extract_histogram_count histo.txt \
'GenRuleAction$')"
if [[ "$genrule_action_count" -lt 1 ]]; then
diff --git a/src/test/shell/integration/starlark_configurations_test.sh b/src/test/shell/integration/starlark_configurations_test.sh
index e0b2e59..8004a9e 100755
--- a/src/test/shell/integration/starlark_configurations_test.sh
+++ b/src/test/shell/integration/starlark_configurations_test.sh
@@ -223,7 +223,196 @@
expect_log "type=cowabunga"
}
-# Test that label-typed build setting has access to providers of the target it points to.
+# Regression tests for b/134580627
+# Make sure package incrementality works during options parsing
+function test_incremental_delete_build_setting() {
+ local -r pkg=$FUNCNAME
+ mkdir -p $pkg
+
+ cat > $pkg/rules.bzl <<EOF
+def _impl(ctx):
+ return []
+
+my_flag = rule(
+ implementation = _impl,
+ build_setting = config.string(flag = True)
+)
+simple_rule = rule(implementation = _impl)
+EOF
+
+ cat > $pkg/BUILD <<EOF
+load("//$pkg:rules.bzl", "my_flag", "simple_rule")
+
+my_flag(name = "my_flag", build_setting_default = "default")
+simple_rule(name = "simple_rule")
+EOF
+
+ bazel build //$pkg:simple_rule --//$pkg:my_flag=cowabunga \
+ --experimental_build_setting_api > output 2>"$TEST_log" \
+ || fail "Expected success"
+
+ cat > $pkg/BUILD <<EOF
+load("//$pkg:rules.bzl", "simple_rule")
+
+simple_rule(name = "simple_rule")
+EOF
+
+ bazel build //$pkg:simple_rule --//$pkg:my_flag=cowabunga \
+ --experimental_build_setting_api > output 2>"$TEST_log" \
+ && fail "Expected failure" || true
+
+ expect_log "no such target '//$pkg:my_flag'"
+}
+
+#############################
+
+function test_incremental_delete_build_setting_in_different_package() {
+ local -r pkg=$FUNCNAME
+ mkdir -p $pkg
+
+ cat > $pkg/rules.bzl <<EOF
+def _impl(ctx):
+ return []
+
+my_flag = rule(
+ implementation = _impl,
+ build_setting = config.string(flag = True)
+)
+simple_rule = rule(implementation = _impl)
+EOF
+
+ cat > $pkg/BUILD <<EOF
+load("//$pkg:rules.bzl", "my_flag")
+my_flag(name = "my_flag", build_setting_default = "default")
+EOF
+
+ mkdir -p pkg2
+
+ cat > pkg2/BUILD <<EOF
+load("//$pkg:rules.bzl", "simple_rule")
+simple_rule(name = "simple_rule")
+EOF
+
+ bazel build //pkg2:simple_rule --//$pkg:my_flag=cowabunga \
+ --experimental_build_setting_api > output 2>"$TEST_log" \
+ || fail "Expected success"
+
+ cat > $pkg/BUILD <<EOF
+EOF
+
+ bazel build //pkg2:simple_rule --//$pkg:my_flag=cowabunga \
+ --experimental_build_setting_api > output 2>"$TEST_log" \
+ && fail "Expected failure" || true
+
+ expect_log "no such target '//$pkg:my_flag'"
+}
+
+#############################
+
+
+function test_incremental_add_build_setting() {
+ local -r pkg=$FUNCNAME
+ mkdir -p $pkg
+
+ cat > $pkg/rules.bzl <<EOF
+def _impl(ctx):
+ return []
+
+my_flag = rule(
+ implementation = _impl,
+ build_setting = config.string(flag = True)
+)
+simple_rule = rule(implementation = _impl)
+EOF
+
+ cat > $pkg/BUILD <<EOF
+load("//$pkg:rules.bzl", "simple_rule")
+
+simple_rule(name = "simple_rule")
+EOF
+
+ bazel build //$pkg:simple_rule --experimental_build_setting_api \
+ > output 2>"$TEST_log" || fail "Expected success"
+
+ cat > $pkg/BUILD <<EOF
+load("//$pkg:rules.bzl", "my_flag", "simple_rule")
+
+my_flag(name = "my_flag", build_setting_default = "default")
+simple_rule(name = "simple_rule")
+EOF
+
+ bazel build //$pkg:simple_rule --//$pkg:my_flag=cowabunga \
+ --experimental_build_setting_api > output 2>"$TEST_log" \
+ || fail "Expected success"
+}
+
+function test_incremental_change_build_setting() {
+ local -r pkg=$FUNCNAME
+ mkdir -p $pkg
+
+ cat > $pkg/rules.bzl <<EOF
+MyProvider = provider(fields = ["value"])
+
+def _flag_impl(ctx):
+ return MyProvider(value = ctx.build_setting_value)
+
+my_flag = rule(
+ implementation = _flag_impl,
+ build_setting = config.string(flag = True)
+)
+
+def _rule_impl(ctx):
+ print("flag = " + ctx.attr.flag[MyProvider].value)
+
+simple_rule = rule(
+ implementation = _rule_impl,
+ attrs = {"flag" : attr.label()}
+)
+EOF
+
+ cat > $pkg/BUILD <<EOF
+load("//$pkg:rules.bzl", "my_flag", "simple_rule")
+
+my_flag(name = "my_flag", build_setting_default = "default")
+simple_rule(name = "simple_rule", flag = ":my_flag")
+EOF
+
+ bazel build //$pkg:simple_rule --//$pkg:my_flag=yabadabadoo \
+ --experimental_build_setting_api > output 2>"$TEST_log" \
+ || fail "Expected success"
+
+ expect_log "flag = yabadabadoo"
+
+# update the flag to return a different value
+ cat > $pkg/rules.bzl <<EOF
+MyProvider = provider(fields = ["value"])
+
+def _flag_impl(ctx):
+ return MyProvider(value = "scoobydoobydoo")
+
+my_flag = rule(
+ implementation = _flag_impl,
+ build_setting = config.string(flag = True)
+)
+
+def _rule_impl(ctx):
+ print("flag = " + ctx.attr.flag[MyProvider].value)
+
+simple_rule = rule(
+ implementation = _rule_impl,
+ attrs = {"flag" : attr.label()}
+)
+EOF
+
+ bazel build //$pkg:simple_rule --//$pkg:my_flag=yabadabadoo \
+ --experimental_build_setting_api > output 2>"$TEST_log" \
+ || fail "Expected success"
+
+ expect_log "flag = scoobydoobydoo"
+}
+
+# Test that label-typed build setting has access to providers of the
+# target it points to.
function test_label_flag() {
local -r pkg=$FUNCNAME
mkdir -p $pkg