blob: e435bb0ccec2b35aa6656d18ce2b23e4ccb72e4e [file] [log] [blame]
// Copyright 2017 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.query2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.TargetParsingException;
import com.google.devtools.build.lib.cmdline.TargetPattern;
import com.google.devtools.build.lib.events.ExtendedEventHandler;
import com.google.devtools.build.lib.events.Reporter;
import com.google.devtools.build.lib.packages.RuleTransitionFactory;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.pkgcache.PackageManager;
import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
import com.google.devtools.build.lib.query2.engine.Callback;
import com.google.devtools.build.lib.query2.engine.KeyExtractor;
import com.google.devtools.build.lib.query2.engine.QueryEnvironment;
import com.google.devtools.build.lib.query2.engine.QueryException;
import com.google.devtools.build.lib.query2.engine.QueryExpression;
import com.google.devtools.build.lib.query2.engine.QueryUtil.ThreadSafeMutableKeyExtractorBackedSetImpl;
import com.google.devtools.build.lib.query2.output.AspectResolver;
import com.google.devtools.build.lib.query2.output.CqueryOptions;
import com.google.devtools.build.lib.rules.AliasConfiguredTarget;
import com.google.devtools.build.lib.skyframe.BuildConfigurationValue;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetValue;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.WalkableGraph;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import javax.annotation.Nullable;
/**
* {@link QueryEnvironment} that runs queries over the configured target (analysis) graph.
*
* <p>There is currently a limited way to specify a configuration in the query syntax via {@link
* ConfigFunction}. This currently still limits the user to choosing the 'target', 'host', or null
* configurations. It shouldn't be terribly difficult to expand this with {@link
* OptionsDiffForReconstruction} to handle fully customizable configurations if the need arises in
* the future.
*
* <p>Aspects are also not supported, but probably should be in some fashion.
*/
public class ConfiguredTargetQueryEnvironment
extends PostAnalysisQueryEnvironment<ConfiguredTarget> {
/** Common query functions and cquery specific functions. */
public static final ImmutableList<QueryFunction> FUNCTIONS = populateFunctions();
/** Cquery specific functions. */
public static final ImmutableList<QueryFunction> CQUERY_FUNCTIONS = getCqueryFunctions();
private CqueryOptions cqueryOptions;
private final KeyExtractor<ConfiguredTarget, ConfiguredTargetKey> configuredTargetKeyExtractor;
private final ConfiguredTargetAccessor accessor;
@Override
protected KeyExtractor<ConfiguredTarget, ConfiguredTargetKey> getConfiguredTargetKeyExtractor() {
return configuredTargetKeyExtractor;
}
public ConfiguredTargetQueryEnvironment(
boolean keepGoing,
ExtendedEventHandler eventHandler,
Iterable<QueryFunction> extraFunctions,
BuildConfiguration defaultTargetConfiguration,
BuildConfiguration hostConfiguration,
String parserPrefix,
PathPackageLocator pkgPath,
Supplier<WalkableGraph> walkableGraphSupplier,
Set<Setting> settings) {
super(
keepGoing,
eventHandler,
extraFunctions,
defaultTargetConfiguration,
hostConfiguration,
parserPrefix,
pkgPath,
walkableGraphSupplier,
settings);
this.accessor = new ConfiguredTargetAccessor(walkableGraphSupplier.get(), this);
this.configuredTargetKeyExtractor =
element -> {
try {
return ConfiguredTargetKey.of(
element,
element.getConfigurationKey() == null
? null
: ((BuildConfigurationValue) graph.getValue(element.getConfigurationKey()))
.getConfiguration());
} catch (InterruptedException e) {
throw new IllegalStateException("Interruption unexpected in configured query");
}
};
}
public ConfiguredTargetQueryEnvironment(
boolean keepGoing,
ExtendedEventHandler eventHandler,
Iterable<QueryFunction> extraFunctions,
BuildConfiguration defaultTargetConfiguration,
BuildConfiguration hostConfiguration,
String parserPrefix,
PathPackageLocator pkgPath,
Supplier<WalkableGraph> walkableGraphSupplier,
CqueryOptions cqueryOptions) {
this(keepGoing, eventHandler, extraFunctions, defaultTargetConfiguration, hostConfiguration,
parserPrefix, pkgPath, walkableGraphSupplier, cqueryOptions.toSettings());
this.cqueryOptions = cqueryOptions;
}
private static ImmutableList<QueryFunction> populateFunctions() {
return new ImmutableList.Builder<QueryFunction>()
.addAll(QueryEnvironment.DEFAULT_QUERY_FUNCTIONS)
.addAll(getCqueryFunctions())
.build();
}
private static ImmutableList<QueryFunction> getCqueryFunctions() {
return ImmutableList.of(new ConfigFunction());
}
@Override
public ImmutableList<NamedThreadSafeOutputFormatterCallback<ConfiguredTarget>>
getDefaultOutputFormatters(
TargetAccessor<ConfiguredTarget> accessor,
Reporter reporter,
SkyframeExecutor skyframeExecutor,
BuildConfiguration hostConfiguration,
@Nullable RuleTransitionFactory trimmingTransitionFactory,
PackageManager packageManager) {
AspectResolver aspectResolver =
cqueryOptions.aspectDeps.createResolver(packageManager, reporter);
OutputStream out = reporter.getOutErr().getOutputStream();
return new ImmutableList.Builder<NamedThreadSafeOutputFormatterCallback<ConfiguredTarget>>()
.add(
new LabelAndConfigurationOutputFormatterCallback(
reporter, cqueryOptions, out, skyframeExecutor, accessor))
.add(
new TransitionsOutputFormatterCallback(
reporter,
cqueryOptions,
out,
skyframeExecutor,
accessor,
hostConfiguration,
trimmingTransitionFactory))
.add(
new ProtoOutputFormatterCallback(
reporter, cqueryOptions, out, skyframeExecutor, accessor, aspectResolver))
.build();
}
public String getOutputFormat() {
return cqueryOptions.outputFormat;
}
@Override
public ConfiguredTargetAccessor getAccessor() {
return accessor;
}
@Override
public QueryTaskFuture<Void> getTargetsMatchingPattern(
QueryExpression owner, String pattern, Callback<ConfiguredTarget> callback) {
TargetPattern patternToEval;
try {
patternToEval = getPattern(pattern);
} catch (TargetParsingException tpe) {
try {
reportBuildFileError(owner, tpe.getMessage());
} catch (QueryException qe) {
return immediateFailedFuture(qe);
}
return immediateSuccessfulFuture(null);
}
AsyncFunction<TargetParsingException, Void> reportBuildFileErrorAsyncFunction =
exn -> {
reportBuildFileError(owner, exn.getMessage());
return Futures.immediateFuture(null);
};
return QueryTaskFutureImpl.ofDelegate(
Futures.catchingAsync(
patternToEval.evalAdaptedForAsync(
resolver,
ImmutableSet.of(),
ImmutableSet.of(),
(Callback<Target>)
partialResult -> {
List<ConfiguredTarget> transformedResult = new ArrayList<>();
for (Target target : partialResult) {
ConfiguredTarget configuredTarget = getConfiguredTarget(target.getLabel());
if (configuredTarget != null) {
transformedResult.add(configuredTarget);
}
}
callback.process(transformedResult);
},
QueryException.class),
TargetParsingException.class,
reportBuildFileErrorAsyncFunction,
MoreExecutors.directExecutor()));
}
private ConfiguredTarget getConfiguredTarget(Label label) throws InterruptedException {
// Try with target configuration.
ConfiguredTarget configuredTarget = getTargetConfiguredTarget(label);
if (configuredTarget != null) {
return configuredTarget;
}
// Try with host configuration (even when --nohost_deps is set in the case that top-level
// targets are configured in the host configuration so we are doing a host-configuration-only
// query).
configuredTarget = getHostConfiguredTarget(label);
if (configuredTarget != null) {
return configuredTarget;
}
// Last chance: source file.
return getNullConfiguredTarget(label);
}
@Override
@Nullable
protected ConfiguredTarget getValueFromKey(SkyKey key) throws InterruptedException {
ConfiguredTargetValue value = getConfiguredTargetValue(key);
return value == null ? null : value.getConfiguredTarget();
}
/**
* Processes the targets in {@code targets} with the requested {@code configuration}
*
* @param pattern the original pattern that {@code targets} were parsed from. Used for error
* message.
* @param targets the set of {@link ConfiguredTarget}s whose labels represent the targets being
* requested.
* @param configuration the configuration to request {@code targets} in.
* @param callback the callback to receive the results of this method.
* @return {@link QueryTaskCallable} that returns the correctly configured targets.
*/
QueryTaskCallable<Void> getConfiguredTargets(
String pattern,
ThreadSafeMutableSet<ConfiguredTarget> targets,
String configuration,
Callback<ConfiguredTarget> callback) {
return new QueryTaskCallable<Void>() {
@Override
public Void call() throws QueryException, InterruptedException {
List<ConfiguredTarget> transformedResult = new ArrayList<>();
for (ConfiguredTarget target : targets) {
Label label = getCorrectLabel(target);
ConfiguredTarget configuredTarget;
switch (configuration) {
case "\'host\'":
configuredTarget = getHostConfiguredTarget(label);
break;
case "\'target\'":
configuredTarget = getTargetConfiguredTarget(label);
break;
case "\'null\'":
configuredTarget = getNullConfiguredTarget(label);
break;
default:
throw new QueryException(
"the second argument of the config function must be 'target', 'host', or 'null'");
}
if (configuredTarget != null) {
transformedResult.add(configuredTarget);
}
}
if (transformedResult.isEmpty()) {
throw new QueryException(
"No target (in) "
+ pattern
+ " could be found in the "
+ configuration
+ " configuration");
}
callback.process(transformedResult);
return null;
}
};
}
/**
* This method has to exist because {@link AliasConfiguredTarget#getLabel()} returns the label of
* the "actual" target instead of the alias target. Grr.
*/
@Override
public Label getCorrectLabel(ConfiguredTarget target) {
if (target instanceof AliasConfiguredTarget) {
return ((AliasConfiguredTarget) target).getOriginalLabel();
}
return target.getLabel();
}
@Nullable
@Override
protected ConfiguredTarget getHostConfiguredTarget(Label label) throws InterruptedException {
return getValueFromKey(ConfiguredTargetValue.key(label, hostConfiguration));
}
@Nullable
@Override
protected ConfiguredTarget getTargetConfiguredTarget(Label label) throws InterruptedException {
return getValueFromKey(ConfiguredTargetValue.key(label, defaultTargetConfiguration));
}
@Nullable
@Override
protected ConfiguredTarget getNullConfiguredTarget(Label label) throws InterruptedException {
return getValueFromKey(ConfiguredTargetValue.key(label, null));
}
@Nullable
@Override
protected RuleConfiguredTarget getRuleConfiguredTarget(ConfiguredTarget configuredTarget) {
if (configuredTarget instanceof RuleConfiguredTarget) {
return (RuleConfiguredTarget) configuredTarget;
}
return null;
}
@Nullable
@Override
protected BuildConfiguration getConfiguration(ConfiguredTarget target) {
try {
return target.getConfigurationKey() == null
? null
: ((BuildConfigurationValue) graph.getValue(target.getConfigurationKey()))
.getConfiguration();
} catch (InterruptedException e) {
throw new IllegalStateException("Unexpected interruption during configured target query");
}
}
@Override
protected ConfiguredTargetKey getSkyKey(ConfiguredTarget target) {
return ConfiguredTargetKey.of(target, getConfiguration(target));
}
@Override
public ThreadSafeMutableSet<ConfiguredTarget> createThreadSafeMutableSet() {
return new ThreadSafeMutableKeyExtractorBackedSetImpl<>(
configuredTargetKeyExtractor,
ConfiguredTarget.class,
SkyQueryEnvironment.DEFAULT_THREAD_COUNT);
}
}