blob: 71df720e849966b885e923a1fb6da2c43736cdf7 [file] [log] [blame]
// 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 com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.buildtool.BuildRequest;
import com.google.devtools.build.lib.buildtool.BuildResult;
import com.google.devtools.build.lib.buildtool.BuildTool;
import com.google.devtools.build.lib.buildtool.CqueryProcessor;
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.query2.NamedThreadSafeOutputFormatterCallback;
import com.google.devtools.build.lib.query2.common.CqueryNode;
import com.google.devtools.build.lib.query2.cquery.ConfiguredTargetQueryEnvironment;
import com.google.devtools.build.lib.query2.engine.QueryEnvironment.QueryFunction;
import com.google.devtools.build.lib.query2.engine.QueryExpression;
import com.google.devtools.build.lib.query2.engine.QueryParser;
import com.google.devtools.build.lib.query2.engine.QuerySyntaxException;
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.common.options.OptionPriority.PriorityCategory;
import com.google.devtools.common.options.OptionsParser;
import com.google.devtools.common.options.OptionsParsingException;
import com.google.devtools.common.options.OptionsParsingResult;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
/** Fetches all repos needed for building a given set of targets. */
public class TargetFetcher {
private final CommandEnvironment env;
private TargetFetcher(CommandEnvironment env) {
this.env = env;
}
/** Uses cquery to find and fetch all repos needed to build these targets */
public static void fetchTargets(
CommandEnvironment env, OptionsParsingResult options, List<String> targets)
throws RepositoryMappingResolutionException, InterruptedException, TargetFetcherException {
new TargetFetcher(env).fetchTargets(options, targets);
}
private void fetchTargets(OptionsParsingResult options, List<String> targets)
throws InterruptedException, TargetFetcherException, RepositoryMappingResolutionException {
QueryExpression expr = createQueryExpression(targets);
BuildRequest request = createBuildRequest(env, options, targets);
TargetPattern.Parser mainRepoTargetParser = getMainRepoMappingParser(env);
BuildResult result =
new BuildTool(
env,
new CqueryProcessor(
expr, mainRepoTargetParser, Optional.of(createNoOutputFormatter())))
.processRequest(request, /* validator= */ null);
if (!result.getSuccess()) {
throw new TargetFetcherException(
String.format(
"Fetching some target dependencies for %s failed, but --keep_going specified. "
+ " Ignoring errors",
expr));
}
}
/** Creates special output formatter for fetch that doesn't print anything */
private NamedThreadSafeOutputFormatterCallback<CqueryNode> createNoOutputFormatter() {
return new NamedThreadSafeOutputFormatterCallback<CqueryNode>() {
@Override
public String getName() {
return "no_output";
}
@Override
public void processOutput(Iterable<CqueryNode> partialResult) {
// Just do nothing!
// This will be later used to collect repos for vendoring
}
};
}
private BuildRequest createBuildRequest(
CommandEnvironment env, OptionsParsingResult options, List<String> targets) {
return BuildRequest.builder()
.setCommandName(env.getCommandName())
.setId(env.getCommandId())
.setOptions(options)
.setStartupOptions(env.getRuntime().getStartupOptionsProvider())
.setOutErr(env.getReporter().getOutErr())
.setTargets(targets)
.setStartTimeMillis(env.getCommandStartTime())
.setCheckforActionConflicts(false)
.setReportIncompatibleTargets(false)
.build();
}
private Parser getMainRepoMappingParser(CommandEnvironment env)
throws RepositoryMappingResolutionException, InterruptedException {
RepositoryMapping repoMapping =
env.getSkyframeExecutor()
.getMainRepoMapping(
env.getOptions().getOptions(KeepGoingOption.class).keepGoing,
env.getOptions().getOptions(LoadingPhaseThreadsOption.class).threads,
env.getReporter());
return new Parser(env.getRelativeWorkingDirectory(), RepositoryName.MAIN, repoMapping);
}
private QueryExpression createQueryExpression(List<String> targets)
throws TargetFetcherException {
String query = "deps(" + Joiner.on(" union ").join(targets) + ")";
ImmutableMap<String, QueryFunction> functions =
Stream.of(ConfiguredTargetQueryEnvironment.FUNCTIONS, env.getRuntime().getQueryFunctions())
.flatMap(Collection::stream)
.collect(toImmutableMap(QueryFunction::getName, Function.identity()));
try {
return QueryParser.parse(query, functions);
} catch (QuerySyntaxException e) {
throw new TargetFetcherException(
String.format(
"Fetching target dependencies for %s encountered an error: %s",
QueryExpression.truncate(query), e.getMessage()));
}
}
static void injectOptionsToFetchTarget(OptionsParser optionsParser) {
try {
optionsParser.parse(
PriorityCategory.COMPUTED_DEFAULT,
"Options required to fetch target",
ImmutableList.of("--nobuild"));
optionsParser.parse(
PriorityCategory.COMPUTED_DEFAULT,
"Fetch target should include 'tags = [\"manual\"]' targets by default",
ImmutableList.of("--build_manual_tests"));
optionsParser.parse(
PriorityCategory.SOFTWARE_REQUIREMENT,
"Fetch target should not exclude test_suite rules",
ImmutableList.of("--noexpand_test_suites"));
optionsParser.parse(
PriorityCategory.SOFTWARE_REQUIREMENT,
"Fetch target should not exclude tests",
ImmutableList.of("--nobuild_tests_only"));
} catch (OptionsParsingException e) {
throw new IllegalStateException("Fetch target needed options failed to parse", e);
}
}
static class TargetFetcherException extends Exception {
public TargetFetcherException(String message) {
super(message);
}
}
}