// 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.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. * *

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. * *

Aspects are also not supported, but probably should be in some fashion. */ public class ConfiguredTargetQueryEnvironment extends PostAnalysisQueryEnvironment { /** Common query functions and cquery specific functions. */ public static final ImmutableList FUNCTIONS = populateFunctions(); /** Cquery specific functions. */ public static final ImmutableList CQUERY_FUNCTIONS = getCqueryFunctions(); private CqueryOptions cqueryOptions; private final KeyExtractor configuredTargetKeyExtractor; @Override protected KeyExtractor getConfiguredTargetKeyExtractor() { return configuredTargetKeyExtractor; } public ConfiguredTargetQueryEnvironment( boolean keepGoing, ExtendedEventHandler eventHandler, Iterable extraFunctions, BuildConfiguration defaultTargetConfiguration, BuildConfiguration hostConfiguration, String parserPrefix, PathPackageLocator pkgPath, Supplier walkableGraphSupplier, Set settings) { super( keepGoing, eventHandler, extraFunctions, defaultTargetConfiguration, hostConfiguration, parserPrefix, pkgPath, walkableGraphSupplier, settings, new ConfiguredTargetAccessor(walkableGraphSupplier.get())); 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 extraFunctions, BuildConfiguration defaultTargetConfiguration, BuildConfiguration hostConfiguration, String parserPrefix, PathPackageLocator pkgPath, Supplier walkableGraphSupplier, CqueryOptions cqueryOptions) { this(keepGoing, eventHandler, extraFunctions, defaultTargetConfiguration, hostConfiguration, parserPrefix, pkgPath, walkableGraphSupplier, cqueryOptions.toSettings()); this.cqueryOptions = cqueryOptions; } private static ImmutableList populateFunctions() { return new ImmutableList.Builder() .addAll(QueryEnvironment.DEFAULT_QUERY_FUNCTIONS) .addAll(getCqueryFunctions()) .build(); } private static ImmutableList getCqueryFunctions() { return ImmutableList.of(new ConfigFunction()); } @Override public ImmutableList getDefaultOutputFormatters( TargetAccessor 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() .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 QueryTaskFuture getTargetsMatchingPattern( QueryExpression owner, String pattern, Callback 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 reportBuildFileErrorAsyncFunction = exn -> { reportBuildFileError(owner, exn.getMessage()); return Futures.immediateFuture(null); }; return QueryTaskFutureImpl.ofDelegate( Futures.catchingAsync( patternToEval.evalAdaptedForAsync( resolver, ImmutableSet.of(), ImmutableSet.of(), (Callback) partialResult -> { List 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 getConfiguredTargets( String pattern, ThreadSafeMutableSet targets, String configuration, Callback callback) { return new QueryTaskCallable() { @Override public Void call() throws QueryException, InterruptedException { List 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 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 createThreadSafeMutableSet() { return new ThreadSafeMutableKeyExtractorBackedSetImpl<>( configuredTargetKeyExtractor, ConfiguredTarget.class, SkyQueryEnvironment.DEFAULT_THREAD_COUNT); } }