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

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 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 createThreadSafeMutableSet() { return new ThreadSafeMutableKeyExtractorBackedSetImpl<>( configuredTargetKeyExtractor, ConfiguredTarget.class, SkyQueryEnvironment.DEFAULT_THREAD_COUNT); } }