Skip to content

Commit

Permalink
Add vendor option to allow vendoring one/multiple specific repos
Browse files Browse the repository at this point in the history
When `--repo={repo}` is specified once or more, only the specified repos will be fetched into the vendor directory.

PiperOrigin-RevId: 604934315
Change-Id: Ieec57bb0a24fe3d7a120f78e714b6d9459786e3f
  • Loading branch information
SalmaSamy authored and copybara-github committed Feb 7, 2024
1 parent c7a8610 commit 30b4806
Show file tree
Hide file tree
Showing 6 changed files with 379 additions and 135 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ public InvalidArgumentException(String message, Code code) {
this.code = code;
}

public InvalidArgumentException(String message) {
this(message, Code.INVALID_ARGUMENTS);
}

public Code getCode() {
return code;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
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.LabelSyntaxException;
import com.google.devtools.build.lib.bazel.commands.RepositoryFetcher.RepositoryFetcherException;
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.cmdline.TargetPattern;
Expand Down Expand Up @@ -62,13 +63,11 @@
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.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.common.options.OptionsParsingResult;
import java.io.IOException;
import java.util.EnumSet;
import java.util.List;
import net.starlark.java.eval.EvalException;

/** Fetches external repositories. Which is so fetch. */
@Command(
Expand Down Expand Up @@ -129,12 +128,21 @@ public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult opti
PrecomputedValue.injected(
RepositoryDelegatorFunction.FORCE_FETCH, env.getCommandId().toString())));
}
if (fetchOptions.all || fetchOptions.configure) {
try {
env.syncPackageLoading(options);
if (fetchOptions.all || fetchOptions.configure) {
result = fetchAll(env, options, threadsOption, fetchOptions.configure);
} else if (!fetchOptions.repos.isEmpty()) {
result = fetchRepo(env, options, threadsOption, fetchOptions.repos);
} else {
result = fetchTarget(env, options, threadsOption);
} else if (!fetchOptions.repos.isEmpty()) {
result = fetchRepos(env, threadsOption, fetchOptions.repos);
} else {
result = fetchTarget(env, options, threadsOption);
}
} catch (AbruptExitException e) {
return createFailedBlazeCommandResult(
env.getReporter(), e.getMessage(), e.getDetailedExitCode());
} catch (InterruptedException e) {
return createFailedBlazeCommandResult(
env.getReporter(), "Fetch interrupted: " + e.getMessage());
}
env.getEventBus()
.post(
Expand All @@ -147,7 +155,8 @@ private BlazeCommandResult fetchAll(
CommandEnvironment env,
OptionsParsingResult options,
LoadingPhaseThreadsOption threadsOption,
boolean configureEnabled) {
boolean configureEnabled)
throws InterruptedException {
if (!options.getOptions(BuildLanguageOptions.class).enableBzlmod) {
return createFailedBlazeCommandResult(
env.getReporter(),
Expand All @@ -161,86 +170,45 @@ private BlazeCommandResult fetchAll(
.setEventHandler(env.getReporter())
.build();

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

private BlazeCommandResult fetchRepo(
CommandEnvironment env,
OptionsParsingResult options,
LoadingPhaseThreadsOption threadsOption,
List<String> repos) {
SkyframeExecutor skyframeExecutor = env.getSkyframeExecutor();
EvaluationContext evaluationContext =
EvaluationContext.newBuilder()
.setParallelism(threadsOption.threads)
.setEventHandler(env.getReporter())
.build();
private BlazeCommandResult fetchRepos(
CommandEnvironment env, LoadingPhaseThreadsOption threadsOption, List<String> repos)
throws InterruptedException {
try {
env.syncPackageLoading(options);
ImmutableSet.Builder<SkyKey> repoDelegatorKeys = ImmutableSet.builder();
for (String repo : repos) {
RepositoryName repoName = getRepositoryName(env, threadsOption, repo);
repoDelegatorKeys.add(RepositoryDirectoryValue.key(repoName));
}
EvaluationResult<SkyValue> evaluationResult =
skyframeExecutor.prepareAndGet(repoDelegatorKeys.build(), evaluationContext);
if (evaluationResult.hasError()) {
Exception e = evaluationResult.getError().getException();
return createFailedBlazeCommandResult(
env.getReporter(),
e != null ? e.getMessage() : "Unexpected error during repository fetching.");
}
ImmutableMap<RepositoryName, RepositoryDirectoryValue> repositoryNamesAndValues =
RepositoryFetcher.fetchRepos(repos, env, env.getSkyframeExecutor(), threadsOption);
String notFoundRepos =
repoDelegatorKeys.build().stream()
.filter(
key -> !((RepositoryDirectoryValue) evaluationResult.get(key)).repositoryExists())
.map(key -> ((RepositoryDirectoryValue) evaluationResult.get(key)).getErrorMsg())
repositoryNamesAndValues.values().stream()
.filter(value -> !value.repositoryExists())
.map(value -> value.getErrorMsg())
.collect(joining("; "));
if (!notFoundRepos.isEmpty()) {
return createFailedBlazeCommandResult(
env.getReporter(), "Fetching repos failed with errors: " + notFoundRepos);
}

// Everything has been fetched successfully!
return BlazeCommandResult.success();
} catch (AbruptExitException e) {
return createFailedBlazeCommandResult(
env.getReporter(), "Unknown error: " + e.getMessage(), e.getDetailedExitCode());
} catch (InterruptedException e) {
return createFailedBlazeCommandResult(
env.getReporter(), "Fetch interrupted: " + e.getMessage());
} catch (LabelSyntaxException | EvalException | IllegalArgumentException e) {
return createFailedBlazeCommandResult(
env.getReporter(), "Invalid repo name: " + e.getMessage());
} catch (RepositoryMappingResolutionException e) {
return createFailedBlazeCommandResult(
env.getReporter(), "Invalid repo name: " + e.getMessage(), e.getDetailedExitCode());
} catch (RepositoryFetcherException e) {
return createFailedBlazeCommandResult(env.getReporter(), e.getMessage());
}
}

private BlazeCommandResult fetchTarget(
CommandEnvironment env,
OptionsParsingResult options,
LoadingPhaseThreadsOption threadsOption) {
CommandEnvironment env, OptionsParsingResult options, LoadingPhaseThreadsOption threadsOption)
throws InterruptedException {
if (options.getResidue().isEmpty()) {
return createFailedBlazeCommandResult(
env.getReporter(),
Expand All @@ -253,7 +221,6 @@ private BlazeCommandResult fetchTarget(
boolean keepGoing = options.getOptions(KeepGoingOption.class).keepGoing;
TargetPattern.Parser mainRepoTargetParser;
try {
env.syncPackageLoading(options);
RepositoryMapping repoMapping =
env.getSkyframeExecutor()
.getMainRepoMapping(keepGoing, threadsOption.threads, env.getReporter());
Expand All @@ -263,12 +230,6 @@ private BlazeCommandResult fetchTarget(
} catch (RepositoryMappingResolutionException e) {
return createFailedBlazeCommandResult(
env.getReporter(), e.getMessage(), e.getDetailedExitCode());
} catch (InterruptedException e) {
return createFailedBlazeCommandResult(
env.getReporter(), "Fetch interrupted: " + e.getMessage());
} catch (AbruptExitException e) {
return createFailedBlazeCommandResult(
env.getReporter(), "Unknown error: " + e.getMessage(), e.getDetailedExitCode());
}

// Querying for all of the dependencies of the targets has the side-effect of populating the
Expand Down Expand Up @@ -363,29 +324,6 @@ public void processOutput(Iterable<Target> partialResult) {
expr));
}

private RepositoryName getRepositoryName(
CommandEnvironment env, LoadingPhaseThreadsOption threadsOption, String repoName)
throws EvalException,
LabelSyntaxException,
RepositoryMappingResolutionException,
InterruptedException {
if (repoName.startsWith("@@")) { // canonical RepoName
return RepositoryName.create(repoName.substring(2));
} else if (repoName.startsWith("@")) { // apparent RepoName
RepositoryName.validateUserProvidedRepoName(repoName.substring(1));
RepositoryMapping repoMapping =
env.getSkyframeExecutor()
.getMainRepoMapping(
env.getOptions().getOptions(KeepGoingOption.class).keepGoing,
threadsOption.threads,
env.getReporter());
return repoMapping.get(repoName.substring(1));
} else {
throw new IllegalArgumentException(
"The repo value has to be either apparent '@repo' or canonical '@@repo' repo name");
}
}

private static BlazeCommandResult createFailedBlazeCommandResult(
Reporter reporter, Code fetchCommandCode, String message) {
return createFailedBlazeCommandResult(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright 2024 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 static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.bazel.bzlmod.modcommand.InvalidArgumentException;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.rules.repository.RepositoryDirectoryValue;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import com.google.devtools.build.lib.runtime.KeepGoingOption;
import com.google.devtools.build.lib.runtime.LoadingPhaseThreadsOption;
import com.google.devtools.build.lib.skyframe.RepositoryMappingValue.RepositoryMappingResolutionException;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
import com.google.devtools.build.skyframe.EvaluationContext;
import com.google.devtools.build.skyframe.EvaluationResult;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import java.util.List;
import net.starlark.java.eval.EvalException;

/** Fetches repositories for commands. */
final class RepositoryFetcher {

private final CommandEnvironment env;
private final SkyframeExecutor skyframeExecutor;
private final LoadingPhaseThreadsOption threadsOption;

private RepositoryFetcher(
CommandEnvironment env,
SkyframeExecutor skyframeExecutor,
LoadingPhaseThreadsOption threadsOption) {
this.env = env;
this.skyframeExecutor = skyframeExecutor;
this.threadsOption = threadsOption;
}

static ImmutableMap<RepositoryName, RepositoryDirectoryValue> fetchRepos(
List<String> repos,
CommandEnvironment env,
SkyframeExecutor skyframeExecutor,
LoadingPhaseThreadsOption threadsOption)
throws RepositoryMappingResolutionException,
InterruptedException,
RepositoryFetcherException {
return new RepositoryFetcher(env, skyframeExecutor, threadsOption).fetchRepos(repos);
}

private ImmutableMap<RepositoryName, RepositoryDirectoryValue> fetchRepos(List<String> repos)
throws InterruptedException,
RepositoryFetcherException,
RepositoryMappingResolutionException {
ImmutableSet<RepositoryName> reposnames = collectRepositoryNames(repos);
EvaluationResult<SkyValue> evaluationResult = evaluateFetch(reposnames);
return reposnames.stream()
.collect(
toImmutableMap(
repoName -> repoName,
repoName ->
(RepositoryDirectoryValue)
evaluationResult.get(RepositoryDirectoryValue.key(repoName))));
}

private EvaluationResult<SkyValue> evaluateFetch(ImmutableSet<RepositoryName> reposnames)
throws InterruptedException, RepositoryFetcherException {
EvaluationContext evaluationContext =
EvaluationContext.newBuilder()
.setParallelism(threadsOption.threads)
.setEventHandler(env.getReporter())
.build();
ImmutableSet<SkyKey> repoDelegatorKeys =
reposnames.stream().map(RepositoryDirectoryValue::key).collect(toImmutableSet());
EvaluationResult<SkyValue> evaluationResult =
skyframeExecutor.prepareAndGet(repoDelegatorKeys, evaluationContext);
if (evaluationResult.hasError()) {
Exception e = evaluationResult.getError().getException();
throw new RepositoryFetcherException(
e != null ? e.getMessage() : "Unexpected error during repository fetching.");
}
return evaluationResult;
}

private ImmutableSet<RepositoryName> collectRepositoryNames(List<String> repos)
throws InterruptedException,
RepositoryFetcherException,
RepositoryMappingResolutionException {
ImmutableSet.Builder<RepositoryName> reposnames = ImmutableSet.builder();
for (String repo : repos) {
try {
reposnames.add(getRepositoryName(repo));
} catch (LabelSyntaxException | EvalException | InvalidArgumentException e) {
throw new RepositoryFetcherException("Invalid repo name: " + e.getMessage());
}
}
return reposnames.build();
}

private RepositoryName getRepositoryName(String repoName)
throws EvalException,
InterruptedException,
LabelSyntaxException,
InvalidArgumentException,
RepositoryMappingResolutionException {
if (repoName.startsWith("@@")) { // canonical RepoName
return RepositoryName.create(repoName.substring(2));
} else if (repoName.startsWith("@")) { // apparent RepoName
RepositoryName.validateUserProvidedRepoName(repoName.substring(1));
RepositoryMapping repoMapping =
env.getSkyframeExecutor()
.getMainRepoMapping(
env.getOptions().getOptions(KeepGoingOption.class).keepGoing,
threadsOption.threads,
env.getReporter());
return repoMapping.get(repoName.substring(1));
} else {
throw new InvalidArgumentException(
"The repo value has to be either apparent '@repo' or canonical '@@repo' repo name");
}
}

static class RepositoryFetcherException extends Exception {
public RepositoryFetcherException(String message) {
super(message);
}
}
}
Loading

0 comments on commit 30b4806

Please sign in to comment.