Implement `bazel sync --configure`
...to only unconditionally fetch all configure-like repositories.
Change-Id: I333e998dba1f091e7f487ea21bee49ec5292a8c2
PiperOrigin-RevId: 258364090
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
index d240b9e..c5685bc 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
@@ -350,6 +350,9 @@
// Nevertheless, we need to provide a default value for other commands.
PrecomputedValue.injected(
RepositoryDelegatorFunction.DEPENDENCY_FOR_UNCONDITIONAL_FETCHING,
+ RepositoryDelegatorFunction.DONT_FETCH_UNCONDITIONALLY),
+ PrecomputedValue.injected(
+ RepositoryDelegatorFunction.DEPENDENCY_FOR_UNCONDITIONAL_CONFIGURING,
RepositoryDelegatorFunction.DONT_FETCH_UNCONDITIONALLY));
}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/commands/SyncCommand.java b/src/main/java/com/google/devtools/build/lib/bazel/commands/SyncCommand.java
index ecfa9b8..d942050 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/commands/SyncCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/commands/SyncCommand.java
@@ -19,6 +19,7 @@
import com.google.devtools.build.lib.analysis.NoBuildEvent;
import com.google.devtools.build.lib.analysis.NoBuildRequestFinishedEvent;
import com.google.devtools.build.lib.bazel.repository.RepositoryOrderEvent;
+import com.google.devtools.build.lib.bazel.repository.skylark.SkylarkRepositoryFunction;
import com.google.devtools.build.lib.cmdline.LabelConstants;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.cmdline.RepositoryName;
@@ -57,7 +58,12 @@
/** Syncs all repositories specified in the workspace file */
@Command(
name = SyncCommand.NAME,
- options = {PackageCacheOptions.class, KeepGoingOption.class, LoadingPhaseThreadsOption.class},
+ options = {
+ PackageCacheOptions.class,
+ KeepGoingOption.class,
+ LoadingPhaseThreadsOption.class,
+ SyncOptions.class
+ },
help = "resource:sync.txt",
shortDescription = "Syncs all repositories specified in the workspace file",
allowResidue = false)
@@ -93,11 +99,21 @@
env.getCommandId().toString()));
env.setupPackageCache(options);
SkyframeExecutor skyframeExecutor = env.getSkyframeExecutor();
- skyframeExecutor.injectExtraPrecomputedValues(
- ImmutableList.of(
- PrecomputedValue.injected(
- RepositoryDelegatorFunction.DEPENDENCY_FOR_UNCONDITIONAL_FETCHING,
- env.getCommandId().toString())));
+
+ SyncOptions syncOptions = options.getOptions(SyncOptions.class);
+ if (syncOptions.configure) {
+ skyframeExecutor.injectExtraPrecomputedValues(
+ ImmutableList.of(
+ PrecomputedValue.injected(
+ RepositoryDelegatorFunction.DEPENDENCY_FOR_UNCONDITIONAL_CONFIGURING,
+ env.getCommandId().toString())));
+ } else {
+ skyframeExecutor.injectExtraPrecomputedValues(
+ ImmutableList.of(
+ PrecomputedValue.injected(
+ RepositoryDelegatorFunction.DEPENDENCY_FOR_UNCONDITIONAL_FETCHING,
+ env.getCommandId().toString())));
+ }
// Obtain the key for the top-level WORKSPACE file
SkyKey packageLookupKey = PackageLookupValue.key(LabelConstants.EXTERNAL_PACKAGE_IDENTIFIER);
@@ -167,7 +183,7 @@
// fetch anyway. So the only task remaining is to record the use of "bind" for whoever
// collects resolved information.
env.getReporter().post(resolveBind(rule));
- } else if (shouldSync(rule)) {
+ } else if (shouldSync(rule, syncOptions.configure)) {
// TODO(aehlig): avoid the detour of serializing and then parsing the repository name
try {
repositoriesToFetch.add(
@@ -208,11 +224,16 @@
return BlazeCommandResult.exitCode(exitCode);
}
- private static boolean shouldSync(Rule rule) {
+ private static boolean shouldSync(Rule rule, boolean configure) {
if (!rule.getRuleClassObject().getWorkspaceOnly()) {
// We should only sync workspace rules
return false;
}
+ if (configure) {
+ // If this is only a configure run, only sync Starlark rules that
+ // declare themselves as configure-like.
+ return SkylarkRepositoryFunction.isConfigureRule(rule);
+ }
if (rule.getRuleClassObject().isSkylark()) {
// Skylark rules are all whitelisted
return true;
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/commands/SyncOptions.java b/src/main/java/com/google/devtools/build/lib/bazel/commands/SyncOptions.java
new file mode 100644
index 0000000..0397921
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/commands/SyncOptions.java
@@ -0,0 +1,30 @@
+// Copyright 2019 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.bazel.commands;
+
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionDocumentationCategory;
+import com.google.devtools.common.options.OptionEffectTag;
+import com.google.devtools.common.options.OptionsBase;
+
+/** Defines the options specific to Bazel's sync command */
+public class SyncOptions extends OptionsBase {
+ @Option(
+ name = "configure",
+ defaultValue = "False",
+ documentationCategory = OptionDocumentationCategory.EXECUTION_STRATEGY,
+ effectTags = {OptionEffectTag.CHANGES_INPUTS},
+ help = "Only sync repositories marked as 'configure' for system-configuration purpose.")
+ public boolean configure;
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryFunction.java
index 38f2a9c..4edfa05 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryFunction.java
@@ -249,7 +249,13 @@
return (Boolean) rule.getAttributeContainer().getAttr("$configure");
}
- public static boolean isConfigurelikeRule(Rule rule) {
+ /**
+ * Static method to determine if for a starlark repository rule {@code isConfigure} holds true. It
+ * also checks that the rule is indeed a Starlark rule so that this class is the appropriate
+ * handler for the given rule. As, however, only Starklark rules can be configure rules, this
+ * method can also be used as a universal check.
+ */
+ public static boolean isConfigureRule(Rule rule) {
return rule.getRuleClassObject().isSkylark()
&& ((Boolean) rule.getAttributeContainer().getAttr("$configure"));
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java
index 8e77200..cb4bbce 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java
@@ -74,6 +74,9 @@
new Precomputed<>(
PrecomputedValue.Key.create("dependency_for_unconditional_repository_fetching"));
+ public static final Precomputed<String> DEPENDENCY_FOR_UNCONDITIONAL_CONFIGURING =
+ new Precomputed<>(PrecomputedValue.Key.create("dependency_for_unconditional_configuring"));
+
public static final Precomputed<Optional<RootedPath>> RESOLVED_FILE_FOR_VERIFICATION =
new Precomputed<>(
PrecomputedValue.Key.create("resolved_file_for_external_repository_verification"));
@@ -139,6 +142,7 @@
Map<RepositoryName, PathFragment> overrides = REPOSITORY_OVERRIDES.get(env);
boolean doNotFetchUnconditionally =
DONT_FETCH_UNCONDITIONALLY.equals(DEPENDENCY_FOR_UNCONDITIONAL_FETCHING.get(env));
+ boolean needsConfiguring = false;
Path repoRoot = RepositoryFunction.getExternalRepositoryDirectory(directories)
.getRelative(repositoryName.strippedName());
@@ -163,6 +167,11 @@
return RepositoryDirectoryValue.NO_SUCH_REPOSITORY_VALUE;
}
+ if (handler.isConfigure(rule)) {
+ needsConfiguring =
+ !DONT_FETCH_UNCONDITIONALLY.equals(DEPENDENCY_FOR_UNCONDITIONAL_CONFIGURING.get(env));
+ }
+
byte[] ruleSpecificData = handler.getRuleSpecificMarkerData(rule, env);
if (env.valuesMissing()) {
return null;
@@ -185,10 +194,11 @@
&& managedDirectoriesExist(directories.getWorkspace(), managedDirectories)) {
// For the non-local repositories, check if they are already up-to-date:
// 1) unconditional fetching is not enabled, AND
- // 2) repository directory exists, AND
- // 3) marker file correctly describes the current repository state, AND
- // 4) managed directories, mapped to the repository, exist
- if (doNotFetchUnconditionally && repoRoot.exists()) {
+ // 2) unconditional syncing is not enabled or the rule is not a configure rule, AND
+ // 3) repository directory exists, AND
+ // 4) marker file correctly describes the current repository state, AND
+ // 5) managed directories, mapped to the repository, exist
+ if (!needsConfiguring && doNotFetchUnconditionally && repoRoot.exists()) {
byte[] markerHash = digestWriter.areRepositoryAndMarkerFileConsistent(handler, env);
if (env.valuesMissing()) {
return null;
diff --git a/src/test/shell/bazel/skylark_repository_test.sh b/src/test/shell/bazel/skylark_repository_test.sh
index 84629e1..ce3d635 100755
--- a/src/test/shell/bazel/skylark_repository_test.sh
+++ b/src/test/shell/bazel/skylark_repository_test.sh
@@ -1233,6 +1233,67 @@
expect_log "non_existing = False,False"
}
+function test_configure_like_repos() {
+ cat > repos.bzl <<'EOF'
+def _impl(ctx):
+ print("Executing %s" % (ctx.attr.name,))
+ ref = ctx.path(ctx.attr.reference)
+ # Here we explicitly copy a file where we constructed the name
+ # completely outside any build interfaces, so it is not registered
+ # as a dependency of the external repository.
+ ctx.execute(["cp", "%s.shadow" % (ref,), ctx.path("it.txt")])
+ ctx.file("BUILD", "exports_files(['it.txt'])")
+
+source = repository_rule(
+ implementation = _impl,
+ attrs = {"reference" : attr.label()},
+)
+
+configure = repository_rule(
+ implementation = _impl,
+ attrs = {"reference" : attr.label()},
+ configure = True,
+)
+
+EOF
+ cat > WORKSPACE <<'EOF'
+load("//:repos.bzl", "configure", "source")
+
+configure(name="configure", reference="@//:reference.txt")
+source(name="source", reference="@//:reference.txt")
+EOF
+ cat > BUILD <<'EOF'
+[ genrule(
+ name = name,
+ srcs = ["@%s//:it.txt" % (name,)],
+ outs = ["%s.txt" % (name,)],
+ cmd = "cp $< $@",
+ ) for name in ["source", "configure"] ]
+EOF
+ echo "Just to get the path" > reference.txt
+ echo "initial" > reference.txt.shadow
+
+ bazel build //:source //:configure
+ grep 'initial' `bazel info bazel-genfiles`/source.txt \
+ || fail '//:source not generated properly'
+ grep 'initial' `bazel info bazel-genfiles`/configure.txt \
+ || fail '//:configure not generated properly'
+
+ echo "new value" > reference.txt.shadow
+ bazel sync --configure --experimental_repository_resolved_file=resolved.bzl \
+ 2>&1 || fail "Expected sync --configure to succeed"
+ grep -q 'name.*configure' resolved.bzl \
+ || fail "Expected 'configure' to be synced"
+ grep -q 'name.*source' resolved.bzl \
+ && fail "Expected 'source' not to be synced" || :
+
+ bazel build //:source //:configure
+ grep -q 'initial' `bazel info bazel-genfiles`/source.txt \
+ || fail '//:source did not keep its old value'
+ grep -q 'new value' `bazel info bazel-genfiles`/configure.txt \
+ || fail '//:configure not synced properly'
+}
+
function test_timeout_tunable() {
cat >> $(create_workspace_with_default_repos WORKSPACE) <<'EOF'