blob: 3c0632570d614b7e28abb2079d3800383030cd22 [file] [log] [blame]
// 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.runtime.commands;
import static com.google.devtools.build.lib.packages.Rule.ALL_LABELS;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.analysis.NoBuildEvent;
import com.google.devtools.build.lib.analysis.NoBuildRequestFinishedEvent;
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.profiler.ProfilePhase;
import com.google.devtools.build.lib.profiler.Profiler;
import com.google.devtools.build.lib.query2.common.AbstractBlazeQueryEnvironment;
import com.google.devtools.build.lib.query2.common.UniverseScope;
import com.google.devtools.build.lib.query2.engine.QueryEnvironment;
import com.google.devtools.build.lib.query2.engine.QueryEnvironment.Setting;
import com.google.devtools.build.lib.query2.engine.QueryEvalResult;
import com.google.devtools.build.lib.query2.engine.QueryException;
import com.google.devtools.build.lib.query2.query.output.OutputFormatter;
import com.google.devtools.build.lib.query2.query.output.OutputFormatters;
import com.google.devtools.build.lib.query2.query.output.QueryOptions;
import com.google.devtools.build.lib.query2.query.output.QueryOutputUtils;
import com.google.devtools.build.lib.runtime.BlazeCommand;
import com.google.devtools.build.lib.runtime.BlazeCommandResult;
import com.google.devtools.build.lib.runtime.BlazeRuntime;
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.runtime.QueryRuntimeHelper;
import com.google.devtools.build.lib.runtime.QueryRuntimeHelper.QueryRuntimeHelperException;
import com.google.devtools.build.lib.runtime.TargetProviderForQueryEnvironment;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.server.FailureDetails.Query;
import com.google.devtools.build.lib.skyframe.LoadingPhaseStartedEvent;
import com.google.devtools.build.lib.skyframe.PackageProgressReceiver;
import com.google.devtools.build.lib.skyframe.RepositoryMappingValue.RepositoryMappingResolutionException;
import com.google.devtools.build.lib.skyframe.SkyframeExecutorWrappingWalkableGraph;
import com.google.devtools.build.lib.skyframe.SkyframeTargetPatternEvaluator;
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.devtools.build.lib.util.Either;
import com.google.devtools.build.lib.util.ExitCode;
import com.google.devtools.build.lib.util.InterruptedFailureDetails;
import com.google.devtools.build.skyframe.WalkableGraph;
import com.google.devtools.common.options.OptionsParsingResult;
import com.google.devtools.common.options.TriState;
import java.util.Set;
import java.util.function.Function;
import net.starlark.java.eval.StarlarkSemantics;
/**
* Common methods and utils to set up Blaze Runtime environments for {@link BlazeCommand} which
* requires {@link QueryEnvironment}
*/
public abstract class QueryEnvironmentBasedCommand implements BlazeCommand {
/**
* Exit codes: 0 on successful evaluation. 1 if query evaluation did not complete. 2 if query
* parsing failed. 3 if errors were reported but evaluation produced a partial result (only when
* --keep_going is in effect.)
*/
@Override
public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult options) {
env.getEventBus()
.post(
new NoBuildEvent(
env.getCommandName(),
env.getCommandStartTime(),
/* separateFinishedEvent= */ true,
/* showProgress= */ true,
/* id= */ null));
BlazeCommandResult result = execInternal(env, options);
try {
Profiler.instance().markPhase(ProfilePhase.FINISH);
} catch (InterruptedException e) {
return reportAndCreateInterruptResult(env, "Profile finish operation interrupted");
}
env.getEventBus()
.post(
new NoBuildRequestFinishedEvent(
result.getExitCode(), env.getRuntime().getClock().currentTimeMillis()));
return result;
}
private BlazeCommandResult execInternal(CommandEnvironment env, OptionsParsingResult options) {
BlazeRuntime runtime = env.getRuntime();
QueryOptions queryOptions = options.getOptions(QueryOptions.class);
LoadingPhaseThreadsOption threadsOption = options.getOptions(LoadingPhaseThreadsOption.class);
boolean keepGoing = options.getOptions(KeepGoingOption.class).keepGoing;
TargetPattern.Parser mainRepoTargetParser;
try {
env.syncPackageLoading(options);
RepositoryMapping repoMapping =
env.getSkyframeExecutor()
.getMainRepoMapping(keepGoing, threadsOption.threads, env.getReporter());
mainRepoTargetParser =
new Parser(env.getRelativeWorkingDirectory(), RepositoryName.MAIN, repoMapping);
} catch (RepositoryMappingResolutionException e) {
env.getReporter().handle(Event.error(e.getMessage()));
return BlazeCommandResult.detailedExitCode(e.getDetailedExitCode());
} catch (InterruptedException e) {
return reportAndCreateInterruptResult(env, "query interrupted");
} catch (AbruptExitException e) {
env.getReporter().handle(Event.error(null, "Unknown error: " + e.getMessage()));
return BlazeCommandResult.detailedExitCode(e.getDetailedExitCode());
}
String query = null;
try {
query = QueryOptionHelper.readQuery(queryOptions, options, env, /* allowEmptyQuery =*/ false);
} catch (QueryException e) {
return BlazeCommandResult.failureDetail(e.getFailureDetail());
}
Iterable<OutputFormatter> formatters = runtime.getQueryOutputFormatters();
OutputFormatter formatter =
OutputFormatters.getFormatter(formatters, queryOptions.outputFormat);
if (formatter == null) {
return reportAndCreateFailureResult(
env,
String.format(
"Invalid output format '%s'. Valid values are: %s",
queryOptions.outputFormat, OutputFormatters.formatterNames(formatters)),
Query.Code.OUTPUT_FORMAT_INVALID);
}
Set<Setting> settings = queryOptions.toSettings();
boolean streamResults = QueryOutputUtils.shouldStreamResults(queryOptions, formatter);
boolean useGraphlessQuery =
queryOptions.useGraphlessQuery == TriState.YES
|| (queryOptions.useGraphlessQuery == TriState.AUTO && streamResults);
if (useGraphlessQuery && !streamResults) {
return reportAndCreateFailureResult(
env,
String.format(
"--experimental_graphless_query requires --order_output=no or --order_output=auto and"
+ " an --output option that supports streaming; valid values are: %s",
OutputFormatters.streamingFormatterNames(formatters)),
Query.Code.GRAPHLESS_PREREQ_UNMET);
}
StarlarkSemantics starlarkSemantics =
env.getSkyframeExecutor()
.getEffectiveStarlarkSemantics(env.getOptions().getOptions(BuildLanguageOptions.class));
LabelPrinter labelPrinter =
env.getOptions()
.getOptions(QueryOptions.class)
.getLabelPrinter(starlarkSemantics, mainRepoTargetParser.getRepoMapping());
try (QueryRuntimeHelper queryRuntimeHelper =
env.getRuntime().getQueryRuntimeHelperFactory().create(env)) {
Either<BlazeCommandResult, QueryEvalResult> result;
try (AbstractBlazeQueryEnvironment<Target> queryEnv =
newQueryEnvironment(
env,
keepGoing,
!streamResults,
env.getSkyframeExecutor()
.maybeGetHardcodedUniverseScope()
.orElse(getUniverseScope(queryOptions)),
threadsOption.threads,
settings,
useGraphlessQuery,
mainRepoTargetParser,
labelPrinter)) {
result =
doQuery(
query, env, queryOptions, streamResults, formatter, queryEnv, queryRuntimeHelper);
}
return result.map(
Function.identity(),
queryEvalResult -> {
if (queryEvalResult.isEmpty()) {
env.getReporter().handle(Event.info("Empty results"));
}
try {
queryRuntimeHelper.afterQueryOutputIsWritten();
} catch (QueryRuntimeHelperException e) {
env.getReporter().handle(Event.error(e.getMessage()));
return BlazeCommandResult.detailedExitCode(DetailedExitCode.of(e.getFailureDetail()));
} catch (InterruptedException e) {
return reportAndCreateInterruptResult(env, "query interrupted");
}
if (queryEvalResult.getSuccess()) {
return BlazeCommandResult.success();
}
// The numerical exit code expected by query users in this case is always 3
// (corresponding to ExitCode.PARTIAL_ANALYSIS_FAILURE), which is why the command
// result returned here overrides any numerical code associated with the
// detailedExitCode in the eval result.
return BlazeCommandResult.detailedExitCode(
DetailedExitCode.of(
ExitCode.PARTIAL_ANALYSIS_FAILURE,
queryEvalResult.getDetailedExitCode().getFailureDetail()));
});
} catch (QueryRuntimeHelperException e) {
env.getReporter().handle(Event.error(e.getMessage()));
return BlazeCommandResult.detailedExitCode(DetailedExitCode.of(e.getFailureDetail()));
}
}
private static UniverseScope getUniverseScope(QueryOptions queryOptions) {
if (!queryOptions.universeScope.isEmpty()) {
return UniverseScope.fromUniverseScopeList(ImmutableList.copyOf(queryOptions.universeScope));
}
return queryOptions.inferUniverseScope
? UniverseScope.INFER_FROM_QUERY_EXPRESSION
: UniverseScope.EMPTY;
}
protected abstract Either<BlazeCommandResult, QueryEvalResult> doQuery(
String query,
CommandEnvironment env,
QueryOptions queryOptions,
boolean streamResults,
OutputFormatter formatter,
AbstractBlazeQueryEnvironment<Target> queryEnv,
QueryRuntimeHelper queryRuntimeHelper);
public static AbstractBlazeQueryEnvironment<Target> newQueryEnvironment(
CommandEnvironment env,
boolean keepGoing,
boolean orderedResults,
UniverseScope universeScope,
int loadingPhaseThreads,
Set<Setting> settings,
boolean useGraphlessQuery,
TargetPattern.Parser mainRepoTargetParser,
LabelPrinter labelPrinter) {
WalkableGraph walkableGraph =
SkyframeExecutorWrappingWalkableGraph.of(env.getSkyframeExecutor());
TargetProviderForQueryEnvironment targetProviderForQueryEnvironment =
new TargetProviderForQueryEnvironment(walkableGraph, env.getPackageManager());
PackageProgressReceiver progressReceiver =
env.getSkyframeExecutor().getPackageProgressReceiver();
if (progressReceiver != null) {
progressReceiver.reset();
env.getReporter().post(new LoadingPhaseStartedEvent(progressReceiver));
}
return env.getRuntime()
.getQueryEnvironmentFactory()
.create(
env.getSkyframeExecutor().getQueryTransitivePackagePreloader(),
env.getSkyframeExecutor(),
targetProviderForQueryEnvironment,
env.getPackageManager(),
new SkyframeTargetPatternEvaluator(env.getSkyframeExecutor()),
mainRepoTargetParser,
env.getRelativeWorkingDirectory(),
keepGoing,
/* strictScope= */ true,
orderedResults,
universeScope,
loadingPhaseThreads,
/* labelFilter= */ ALL_LABELS,
env.getReporter(),
settings,
env.getRuntime().getQueryFunctions(),
env.getPackageManager().getPackagePath(),
/* blockUniverseEvaluationErrors= */ false,
useGraphlessQuery,
labelPrinter);
}
private static BlazeCommandResult reportAndCreateInterruptResult(
CommandEnvironment env, String message) {
env.getReporter().handle(Event.error(message));
return BlazeCommandResult.detailedExitCode(InterruptedFailureDetails.detailedExitCode(message));
}
private static BlazeCommandResult reportAndCreateFailureResult(
CommandEnvironment env, String message, Query.Code detailedCode) {
env.getReporter().handle(Event.error(message));
return createFailureResult(message, detailedCode);
}
private static BlazeCommandResult createFailureResult(String message, Query.Code detailedCode) {
return BlazeCommandResult.detailedExitCode(
DetailedExitCode.of(
FailureDetail.newBuilder()
.setMessage(message)
.setQuery(Query.newBuilder().setCode(detailedCode))
.build()));
}
}