| // Copyright 2014 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.primitives.Booleans.countTrue; |
| import static java.util.stream.Collectors.joining; |
| |
| 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.bazel.commands.RepositoryFetcher.RepositoryFetcherException; |
| import com.google.devtools.build.lib.bazel.commands.TargetFetcher.TargetFetcherException; |
| import com.google.devtools.build.lib.cmdline.RepositoryName; |
| import com.google.devtools.build.lib.events.Event; |
| import com.google.devtools.build.lib.events.Reporter; |
| import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions; |
| import com.google.devtools.build.lib.pkgcache.PackageOptions; |
| import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction; |
| import com.google.devtools.build.lib.rules.repository.RepositoryDirectoryValue; |
| import com.google.devtools.build.lib.runtime.BlazeCommand; |
| import com.google.devtools.build.lib.runtime.BlazeCommandResult; |
| import com.google.devtools.build.lib.runtime.Command; |
| 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.server.FailureDetails; |
| 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.PrecomputedValue; |
| import com.google.devtools.build.lib.skyframe.RepositoryMappingValue.RepositoryMappingResolutionException; |
| import com.google.devtools.build.lib.util.AbruptExitException; |
| import com.google.devtools.build.lib.util.DetailedExitCode; |
| 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.util.List; |
| import javax.annotation.Nullable; |
| |
| /** Fetches external repositories. Which is so fetch. */ |
| @Command( |
| name = FetchCommand.NAME, |
| 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, |
| completion = "label") |
| public final class FetchCommand implements BlazeCommand { |
| |
| public static final String NAME = "fetch"; |
| |
| @Override |
| public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult options) { |
| BlazeCommandResult invalidResult = validateOptions(env, options); |
| if (invalidResult != null) { |
| return invalidResult; |
| } |
| |
| env.getEventBus() |
| .post( |
| new NoBuildEvent( |
| env.getCommandName(), |
| env.getCommandStartTime(), |
| /* separateFinishedEvent= */ true, |
| /* showProgress= */ true, |
| env.getCommandId().toString())); |
| |
| FetchOptions fetchOptions = options.getOptions(FetchOptions.class); |
| if (fetchOptions.force) { |
| // Using commandId as the value -instead of true/false- to make sure to invalidate skyframe |
| // and to actually force fetch each time |
| env.getSkyframeExecutor() |
| .injectExtraPrecomputedValues( |
| ImmutableList.of( |
| PrecomputedValue.injected( |
| RepositoryDelegatorFunction.FORCE_FETCH, env.getCommandId().toString()))); |
| } |
| |
| BlazeCommandResult result; |
| LoadingPhaseThreadsOption threadsOption = options.getOptions(LoadingPhaseThreadsOption.class); |
| try { |
| env.syncPackageLoading(options); |
| if (!options.getResidue().isEmpty()) { |
| result = fetchTarget(env, options, options.getResidue()); |
| } else if (!fetchOptions.repos.isEmpty()) { |
| result = fetchRepos(env, threadsOption, fetchOptions.repos); |
| } else { // --all, --configure, or just 'fetch' |
| result = fetchAll(env, threadsOption, fetchOptions.configure); |
| } |
| } 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( |
| new NoBuildRequestFinishedEvent( |
| result.getExitCode(), env.getRuntime().getClock().currentTimeMillis())); |
| return result; |
| } |
| |
| @Nullable |
| private BlazeCommandResult validateOptions(CommandEnvironment env, OptionsParsingResult options) { |
| PackageOptions pkgOptions = options.getOptions(PackageOptions.class); |
| if (!pkgOptions.fetch) { |
| return createFailedBlazeCommandResult( |
| env.getReporter(), Code.OPTIONS_INVALID, "You cannot run fetch with --nofetch"); |
| } |
| FetchOptions fetchOptions = options.getOptions(FetchOptions.class); |
| // Only fetch targets works without bzlmod, other than that, fail. |
| if (options.getResidue().isEmpty() |
| && !options.getOptions(BuildLanguageOptions.class).enableBzlmod) { |
| return createFailedBlazeCommandResult( |
| env.getReporter(), |
| "Bzlmod has to be enabled for the following options to work: --all, " |
| + "--configure, --repo or --force. Run with --enable_bzlmod"); |
| } |
| int optionsCount = |
| countTrue( |
| fetchOptions.all, |
| fetchOptions.configure, |
| !fetchOptions.repos.isEmpty(), |
| !options.getResidue().isEmpty()); |
| if (optionsCount > 1) { |
| return createFailedBlazeCommandResult( |
| env.getReporter(), |
| Code.OPTIONS_INVALID, |
| "Only one fetch option can be provided for fetch command"); |
| } |
| return null; |
| } |
| |
| private BlazeCommandResult fetchAll( |
| CommandEnvironment env, |
| LoadingPhaseThreadsOption threadsOption, |
| boolean configureEnabled) |
| throws InterruptedException { |
| EvaluationContext evaluationContext = |
| EvaluationContext.newBuilder() |
| .setParallelism(threadsOption.threads) |
| .setEventHandler(env.getReporter()) |
| .build(); |
| |
| EvaluationResult<SkyValue> evaluationResult = |
| env.getSkyframeExecutor() |
| .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."); |
| } |
| |
| env.getReporter().handle(Event.info("All external dependencies fetched successfully.")); |
| return BlazeCommandResult.success(); |
| } |
| |
| private BlazeCommandResult fetchRepos( |
| CommandEnvironment env, LoadingPhaseThreadsOption threadsOption, List<String> repos) |
| throws InterruptedException { |
| ImmutableMap<RepositoryName, RepositoryDirectoryValue> repositoryNamesAndValues; |
| try { |
| repositoryNamesAndValues = RepositoryFetcher.fetchRepos(repos, env, threadsOption); |
| } catch (RepositoryMappingResolutionException e) { |
| return createFailedBlazeCommandResult( |
| env.getReporter(), "Invalid repo name: " + e.getMessage(), e.getDetailedExitCode()); |
| } catch (RepositoryFetcherException e) { |
| return createFailedBlazeCommandResult(env.getReporter(), e.getMessage()); |
| } |
| |
| String notFoundRepos = |
| repositoryNamesAndValues.values().stream() |
| .filter(value -> !value.repositoryExists()) |
| .map(value -> value.getErrorMsg()) |
| .collect(joining("; ")); |
| if (!notFoundRepos.isEmpty()) { |
| return createFailedBlazeCommandResult( |
| env.getReporter(), "Fetching some repos failed with errors: " + notFoundRepos); |
| } |
| env.getReporter().handle(Event.info("All requested repos fetched successfully.")); |
| return BlazeCommandResult.success(); |
| } |
| |
| private BlazeCommandResult fetchTarget( |
| CommandEnvironment env, OptionsParsingResult options, List<String> targets) |
| throws InterruptedException { |
| try { |
| TargetFetcher.fetchTargets(env, options, targets); |
| } catch (TargetFetcherException e) { |
| return createFailedBlazeCommandResult( |
| env.getReporter(), Code.QUERY_EVALUATION_ERROR, e.getMessage()); |
| } catch (RepositoryMappingResolutionException e) { |
| return createFailedBlazeCommandResult( |
| env.getReporter(), e.getMessage(), e.getDetailedExitCode()); |
| } |
| |
| env.getReporter() |
| .handle(Event.info("All external dependencies for these targets fetched successfully.")); |
| return BlazeCommandResult.success(); |
| } |
| |
| private static BlazeCommandResult createFailedBlazeCommandResult( |
| Reporter reporter, Code fetchCommandCode, String message) { |
| return createFailedBlazeCommandResult( |
| reporter, |
| message, |
| DetailedExitCode.of( |
| FailureDetail.newBuilder() |
| .setMessage(message) |
| .setFetchCommand( |
| FailureDetails.FetchCommand.newBuilder().setCode(fetchCommandCode).build()) |
| .build())); |
| } |
| |
| private static BlazeCommandResult createFailedBlazeCommandResult( |
| Reporter reporter, String errorMessage) { |
| return createFailedBlazeCommandResult( |
| reporter, errorMessage, InterruptedFailureDetails.detailedExitCode(errorMessage)); |
| } |
| |
| private static BlazeCommandResult createFailedBlazeCommandResult( |
| Reporter reporter, String message, DetailedExitCode exitCode) { |
| reporter.handle(Event.error(message)); |
| return BlazeCommandResult.detailedExitCode(exitCode); |
| } |
| } |