Skip to content

Commit

Permalink
Update fetch command to fetch everything
Browse files Browse the repository at this point in the history
- Added fetch option --all
- Update fetch command logic to handle the new option

PiperOrigin-RevId: 567413867
Change-Id: Idb9cd41b0e8fba8b29e2ff669af4096d22f0eea3
  • Loading branch information
SalmaSamy authored and Copybara-Service committed Sep 21, 2023
1 parent fc3460e commit 0356a3f
Show file tree
Hide file tree
Showing 12 changed files with 391 additions and 13 deletions.
Expand Up @@ -36,6 +36,7 @@
import com.google.devtools.build.lib.authandtls.credentialhelper.CredentialModule;
import com.google.devtools.build.lib.bazel.bzlmod.AttributeValues;
import com.google.devtools.build.lib.bazel.bzlmod.BazelDepGraphFunction;
import com.google.devtools.build.lib.bazel.bzlmod.BazelFetchAllFunction;
import com.google.devtools.build.lib.bazel.bzlmod.BazelLockFileFunction;
import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleInspectorFunction;
import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleInspectorValue.AugmentedModule.ResolutionReason;
Expand Down Expand Up @@ -276,6 +277,7 @@ public ResolutionReason getResolutionReason() {
.addSkyFunction(SkyFunctions.BAZEL_DEP_GRAPH, new BazelDepGraphFunction())
.addSkyFunction(
SkyFunctions.BAZEL_LOCK_FILE, new BazelLockFileFunction(directories.getWorkspace()))
.addSkyFunction(SkyFunctions.BAZEL_FETCH_ALL, new BazelFetchAllFunction())
.addSkyFunction(SkyFunctions.BAZEL_MODULE_INSPECTION, new BazelModuleInspectorFunction())
.addSkyFunction(SkyFunctions.BAZEL_MODULE_RESOLUTION, new BazelModuleResolutionFunction())
.addSkyFunction(SkyFunctions.SINGLE_EXTENSION_EVAL, singleExtensionEvalFunction)
Expand Down
Expand Up @@ -115,6 +115,7 @@ java_library(
"AbridgedModule.java",
"ArchiveOverride.java",
"BazelDepGraphValue.java",
"BazelFetchAllValue.java",
"BazelLockFileValue.java",
"BazelModuleResolutionEvent.java",
"BazelModuleResolutionValue.java",
Expand Down Expand Up @@ -167,6 +168,7 @@ java_library(
srcs = [
"AttributeValuesAdapter.java",
"BazelDepGraphFunction.java",
"BazelFetchAllFunction.java",
"BazelLockFileFunction.java",
"BazelModuleResolutionFunction.java",
"BzlmodFlagsAndEnvVars.java",
Expand Down
@@ -0,0 +1,85 @@
// Copyright 2023 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.bzlmod;

import static com.google.common.collect.ImmutableSet.toImmutableSet;

import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.rules.repository.RepositoryDirectoryValue;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.build.skyframe.SkyframeLookupResult;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;

/**
* Void function designed to gather and fetch all the repositories without returning any specific
* result (empty value is returned).
*/
public class BazelFetchAllFunction implements SkyFunction {

@Override
@Nullable
public SkyValue compute(SkyKey skyKey, Environment env) throws InterruptedException {

// Collect all the repos we want to fetch here
List<RepositoryName> reposToFetch = new ArrayList<>();

// 1. Run resolution and collect the dependency graph repos
BazelDepGraphValue depGraphValue = (BazelDepGraphValue) env.getValue(BazelDepGraphValue.KEY);
if (depGraphValue == null) {
return null;
}
reposToFetch.addAll(depGraphValue.getCanonicalRepoNameLookup().keySet());

// 2. Run every extension found in the modules
ImmutableSet<ModuleExtensionId> extensionIds =
depGraphValue.getExtensionUsagesTable().rowKeySet();
ImmutableSet<SkyKey> singleEvalKeys =
extensionIds.stream().map(SingleExtensionEvalValue::key).collect(toImmutableSet());
SkyframeLookupResult singleEvalValues = env.getValuesAndExceptions(singleEvalKeys);

// 3. For each extension, collect its generated repos
for (SkyKey singleEvalKey : singleEvalKeys) {
SingleExtensionEvalValue singleEvalValue =
(SingleExtensionEvalValue) singleEvalValues.get(singleEvalKey);
if (singleEvalValue == null) {
return null;
}
reposToFetch.addAll(singleEvalValue.getCanonicalRepoNameToInternalNames().keySet());
}

// 4. Fetch all the collected repos excluding root
ImmutableSet<SkyKey> repoDelegatorKeys =
reposToFetch.stream()
.filter(repoName -> !repoName.isMain())
.map(RepositoryDirectoryValue::key)
.collect(toImmutableSet());
SkyframeLookupResult repoDirValues = env.getValuesAndExceptions(repoDelegatorKeys);
for (SkyKey repoDelegatorKey : repoDelegatorKeys) {
RepositoryDirectoryValue repoDirValue =
(RepositoryDirectoryValue) repoDirValues.get(repoDelegatorKey);
if (repoDirValue == null) {
return null;
}
}

return BazelFetchAllValue.create();
}
}
@@ -0,0 +1,35 @@
// Copyright 2023 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.bzlmod;

import com.google.auto.value.AutoValue;
import com.google.devtools.build.lib.skyframe.SkyFunctions;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;

/**
* Empty result of running Bazel fetch all dependencies, to indicate that all repos have been
* fetched successfully.
*/
@AutoValue
public abstract class BazelFetchAllValue implements SkyValue {
@SerializationConstant public static final SkyKey KEY = () -> SkyFunctions.BAZEL_FETCH_ALL;

public static BazelFetchAllValue create() {
return new AutoValue_BazelFetchAllValue();
}
}
Expand Up @@ -41,6 +41,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/events",
"//src/main/java/com/google/devtools/build/lib/packages",
"//src/main/java/com/google/devtools/build/lib/packages:label_printer",
"//src/main/java/com/google/devtools/build/lib/packages/semantics",
"//src/main/java/com/google/devtools/build/lib/pkgcache",
"//src/main/java/com/google/devtools/build/lib/query2/common:abstract-blaze-query-env",
"//src/main/java/com/google/devtools/build/lib/query2/common:universe-scope",
Expand Down
Expand Up @@ -15,15 +15,18 @@

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.analysis.NoBuildEvent;
import com.google.devtools.build.lib.analysis.NoBuildRequestFinishedEvent;
import com.google.devtools.build.lib.bazel.bzlmod.BazelFetchAllValue;
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.cmdline.TargetPattern;
import com.google.devtools.build.lib.cmdline.TargetPattern.Parser;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.packages.LabelPrinter;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
import com.google.devtools.build.lib.pkgcache.PackageOptions;
import com.google.devtools.build.lib.query2.common.AbstractBlazeQueryEnvironment;
import com.google.devtools.build.lib.query2.common.UniverseScope;
Expand All @@ -44,18 +47,27 @@
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.server.FailureDetails.FetchCommand.Code;
import com.google.devtools.build.lib.skyframe.RepositoryMappingValue.RepositoryMappingResolutionException;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.devtools.build.lib.util.ExitCode;
import com.google.devtools.build.lib.util.InterruptedFailureDetails;
import com.google.devtools.build.skyframe.EvaluationContext;
import com.google.devtools.build.skyframe.EvaluationResult;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.common.options.OptionsParsingResult;
import java.io.IOException;
import java.util.EnumSet;

/** Fetches external repositories. Which is so fetch. */
@Command(
name = FetchCommand.NAME,
options = {PackageOptions.class, KeepGoingOption.class, LoadingPhaseThreadsOption.class},
options = {
FetchOptions.class,
PackageOptions.class,
KeepGoingOption.class,
LoadingPhaseThreadsOption.class
},
help = "resource:fetch.txt",
shortDescription = "Fetches external repositories that are prerequisites to the targets.",
allowResidue = true,
Expand All @@ -69,6 +81,86 @@ public final class FetchCommand implements BlazeCommand {

@Override
public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult options) {
PackageOptions pkgOptions = options.getOptions(PackageOptions.class);
if (!pkgOptions.fetch) {
String errorMessage = "You cannot run fetch with --fetch=false";
env.getReporter().handle(Event.error(null, errorMessage));
return createFailedBlazeCommandResult(Code.OPTIONS_INVALID, errorMessage);
}

FetchOptions fetchOptions = options.getOptions(FetchOptions.class);
LoadingPhaseThreadsOption threadsOption = options.getOptions(LoadingPhaseThreadsOption.class);

env.getEventBus()
.post(
new NoBuildEvent(
env.getCommandName(),
env.getCommandStartTime(),
/* separateFinishedEvent= */ true,
/* showProgress= */ true,
/* id= */ null));
BlazeCommandResult result;
if (fetchOptions.all) {
result = fetchAll(env, options, threadsOption);
} else {
result = fetchTarget(env, options, threadsOption);
}
env.getEventBus()
.post(
new NoBuildRequestFinishedEvent(
result.getExitCode(), env.getRuntime().getClock().currentTimeMillis()));
return result;
}

private BlazeCommandResult fetchAll(
CommandEnvironment env,
OptionsParsingResult options,
LoadingPhaseThreadsOption threadsOption) {
if (!options.getOptions(BuildLanguageOptions.class).enableBzlmod) {
String errorMessage =
"Bzlmod has to be enabled for fetch --all to work, run with --enable_bzlmod";
env.getReporter().handle(Event.error(null, errorMessage));
return BlazeCommandResult.detailedExitCode(
InterruptedFailureDetails.detailedExitCode(errorMessage));
}

SkyframeExecutor skyframeExecutor = env.getSkyframeExecutor();
EvaluationContext evaluationContext =
EvaluationContext.newBuilder()
.setParallelism(threadsOption.threads)
.setEventHandler(env.getReporter())
.build();

try {
env.syncPackageLoading(options);
EvaluationResult<SkyValue> evaluationResult =
skyframeExecutor.prepareAndGet(
ImmutableSet.of(BazelFetchAllValue.KEY), evaluationContext);
if (evaluationResult.hasError()) {
Exception e = evaluationResult.getError().getException();
String errorMessage =
e != null ? e.getMessage() : "Unexpected error during fetching all external deps.";
env.getReporter().handle(Event.error(errorMessage));
return BlazeCommandResult.detailedExitCode(
InterruptedFailureDetails.detailedExitCode(errorMessage));
}
// Everything is fetched successfully!
return BlazeCommandResult.success();
} catch (AbruptExitException e) {
env.getReporter().handle(Event.error(null, "Unknown error: " + e.getMessage()));
return BlazeCommandResult.detailedExitCode(e.getDetailedExitCode());
} catch (InterruptedException e) {
String errorMessage = "Fetch interrupted: " + e.getMessage();
env.getReporter().handle(Event.error(errorMessage));
return BlazeCommandResult.detailedExitCode(
InterruptedFailureDetails.detailedExitCode(errorMessage));
}
}

private BlazeCommandResult fetchTarget(
CommandEnvironment env,
OptionsParsingResult options,
LoadingPhaseThreadsOption threadsOption) {
if (options.getResidue().isEmpty()) {
String errorMessage =
String.format(
Expand All @@ -78,11 +170,8 @@ public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult opti
return createFailedBlazeCommandResult(Code.EXPRESSION_MISSING, errorMessage);
}

LoadingPhaseThreadsOption threadsOption = options.getOptions(LoadingPhaseThreadsOption.class);
boolean keepGoing = options.getOptions(KeepGoingOption.class).keepGoing;

TargetPattern.Parser mainRepoTargetParser;

try {
env.syncPackageLoading(options);
RepositoryMapping repoMapping =
Expand All @@ -103,13 +192,6 @@ public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult opti
return BlazeCommandResult.detailedExitCode(e.getDetailedExitCode());
}

PackageOptions pkgOptions = options.getOptions(PackageOptions.class);
if (!pkgOptions.fetch) {
String errorMessage = "You cannot run fetch with --fetch=false";
env.getReporter().handle(Event.error(null, errorMessage));
return createFailedBlazeCommandResult(Code.OPTIONS_INVALID, errorMessage);
}

// Querying for all of the dependencies of the targets has the side-effect of populating the
// Skyframe graph for external targets, which requires downloading them. The JDK is required to
// build everything but isn't counted as a dep in the build graph so we add it manually.
Expand Down
@@ -0,0 +1,38 @@
// Copyright 2023 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 FetchOptions extends OptionsBase {
@Option(
name = "all",
defaultValue = "false",
documentationCategory = OptionDocumentationCategory.EXECUTION_STRATEGY,
effectTags = {OptionEffectTag.CHANGES_INPUTS},
help =
"Fetches all external repositories necessary for building any target or repository. Only"
+ " works when --enable_bzlmod is on.")
public boolean all;

/*TODO(salmasamy) add more options:
* repo: to fetch a specific repo
* force: to force fetch even if a repo exists
* configure: to fetch only the repos marked as configure
*/
}
@@ -1,6 +1,6 @@

Usage: %{product} %{command} [<option> ...] <targets>
Usage: %{product} %{command} [<option> ...] --all or <targets>

Fetches all external dependencies for the targets given.
Fetches all external dependencies, or fetch dependencies only for given targets

%{options}
Expand Up @@ -155,6 +155,8 @@ public final class SkyFunctions {
SkyFunctionName.createHermetic("BAZEL_DEP_GRAPH");
public static final SkyFunctionName BAZEL_LOCK_FILE =
SkyFunctionName.createHermetic("BAZEL_LOCK_FILE");
public static final SkyFunctionName BAZEL_FETCH_ALL =
SkyFunctionName.createHermetic("BAZEL_FETCH_ALL");
public static final SkyFunctionName REPO_SPEC = SkyFunctionName.createNonHermetic("REPO_SPEC");

public static Predicate<SkyKey> isSkyFunction(SkyFunctionName functionName) {
Expand Down
11 changes: 11 additions & 0 deletions src/test/py/bazel/BUILD
Expand Up @@ -295,6 +295,17 @@ py_test(
],
)

py_test(
name = "bazel_fetch_test",
size = "medium",
srcs = ["bzlmod/bazel_fetch_test.py"],
data = ["//tools/build_defs/repo:http_src"],
deps = [
":bzlmod_test_utils",
":test_base",
],
)

py_test(
name = "bazel_overrides_test",
size = "large",
Expand Down

0 comments on commit 0356a3f

Please sign in to comment.