| // 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); | |
| } | |
| } | |