// Copyright 2015 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.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.TargetParsingException; import com.google.devtools.build.lib.events.ErrorSensingEventHandler; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.ExtendedEventHandler; import com.google.devtools.build.lib.packages.DependencyFilter; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.query2.engine.AbstractQueryEnvironment; import com.google.devtools.build.lib.query2.engine.KeyExtractor; import com.google.devtools.build.lib.query2.engine.OutputFormatterCallback; import com.google.devtools.build.lib.query2.engine.QueryEnvironment; 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.engine.QueryExpression; import com.google.devtools.build.lib.query2.engine.QueryUtil; import com.google.devtools.build.lib.query2.engine.QueryUtil.AggregateAllCallback; import com.google.devtools.build.lib.query2.engine.ThreadSafeOutputFormatterCallback; import com.google.devtools.build.lib.query2.engine.VariableContext; import com.google.devtools.build.lib.util.Preconditions; import java.io.IOException; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; /** * {@link QueryEnvironment} that can evaluate queries to produce a result, and implements as much of * QueryEnvironment as possible while remaining mostly agnostic as to the objects being stored. */ public abstract class AbstractBlazeQueryEnvironment<T> extends AbstractQueryEnvironment<T> { protected ErrorSensingEventHandler eventHandler; protected final boolean keepGoing; protected final boolean strictScope; protected final DependencyFilter dependencyFilter; private final Predicate<Label> labelFilter; protected final Set<Setting> settings; protected final List<QueryFunction> extraFunctions; private static final Logger logger = Logger.getLogger(AbstractBlazeQueryEnvironment.class.getName()); protected AbstractBlazeQueryEnvironment( boolean keepGoing, boolean strictScope, Predicate<Label> labelFilter, ExtendedEventHandler eventHandler, Set<Setting> settings, Iterable<QueryFunction> extraFunctions) { this.eventHandler = new ErrorSensingEventHandler(eventHandler); this.keepGoing = keepGoing; this.strictScope = strictScope; this.dependencyFilter = constructDependencyFilter(settings); this.labelFilter = labelFilter; this.settings = Sets.immutableEnumSet(settings); this.extraFunctions = ImmutableList.copyOf(extraFunctions); } private static DependencyFilter constructDependencyFilter( Set<Setting> settings) { DependencyFilter specifiedFilter = settings.contains(Setting.NO_HOST_DEPS) ? DependencyFilter.NO_HOST_DEPS : DependencyFilter.ALL_DEPS; if (settings.contains(Setting.NO_IMPLICIT_DEPS)) { specifiedFilter = DependencyFilter.and(specifiedFilter, DependencyFilter.NO_IMPLICIT_DEPS); } if (settings.contains(Setting.NO_NODEP_DEPS)) { specifiedFilter = DependencyFilter.and(specifiedFilter, DependencyFilter.NO_NODEP_ATTRIBUTES); } return specifiedFilter; } /** * Used by {@link #evaluateQuery} to evaluate the given {@code expr}. The caller, * {@link #evaluateQuery}, not {@link #evalTopLevelInternal}, is responsible for managing * {@code callback}. */ protected void evalTopLevelInternal(QueryExpression expr, OutputFormatterCallback<T> callback) throws QueryException, InterruptedException { ((QueryTaskFutureImpl<Void>) eval(expr, VariableContext.<T>empty(), callback)).getChecked(); } /** * Evaluate the specified query expression in this environment, streaming results to the given * {@code callback}. {@code callback.start()} will be called before query evaluation and * {@code callback.close()} will be unconditionally called at the end of query evaluation * (i.e. regardless of whether it was successful). * * @return a {@link QueryEvalResult} object that contains the resulting set of targets and a bit * to indicate whether errors occurred during evaluation; note that the * success status can only be false if {@code --keep_going} was in effect * @throws QueryException if the evaluation failed and {@code --nokeep_going} was in * effect */ public QueryEvalResult evaluateQuery( QueryExpression expr, ThreadSafeOutputFormatterCallback<T> callback) throws QueryException, InterruptedException, IOException { EmptinessSensingCallback<T> emptySensingCallback = new EmptinessSensingCallback<>(callback); long startTime = System.currentTimeMillis(); // In the --nokeep_going case, errors are reported in the order in which the patterns are // specified; using a linked hash set here makes sure that the left-most error is reported. Set<String> targetPatternSet = new LinkedHashSet<>(); expr.collectTargetPatterns(targetPatternSet); try { preloadOrThrow(expr, targetPatternSet); } catch (TargetParsingException e) { // Unfortunately, by evaluating the patterns in parallel, we lose some location information. throw new QueryException(expr, e.getMessage()); } IOException ioExn = null; boolean failFast = true; try { callback.start(); evalTopLevelInternal(expr, emptySensingCallback); failFast = false; } catch (QueryException e) { throw new QueryException(e, expr); } catch (InterruptedException e) { throw e; } finally { try { callback.close(failFast); } catch (IOException e) { // Only throw this IOException if we weren't about to throw a different exception. ioExn = e; } } if (ioExn != null) { throw ioExn; } long elapsedTime = System.currentTimeMillis() - startTime; if (elapsedTime > 1) { logger.info("Spent " + elapsedTime + " milliseconds evaluating query"); } if (eventHandler.hasErrors()) { if (!keepGoing) { // This case represents loading-phase errors reported during evaluation // of target patterns that don't cause evaluation to fail per se. throw new QueryException("Evaluation of query \"" + expr + "\" failed due to BUILD file errors"); } else { eventHandler.handle(Event.warn("--keep_going specified, ignoring errors. " + "Results may be inaccurate")); } } return new QueryEvalResult(!eventHandler.hasErrors(), emptySensingCallback.isEmpty()); } private static class EmptinessSensingCallback<T> extends OutputFormatterCallback<T> { private final OutputFormatterCallback<T> callback; private final AtomicBoolean empty = new AtomicBoolean(true); private EmptinessSensingCallback(OutputFormatterCallback<T> callback) { this.callback = callback; } @Override public void start() throws IOException { callback.start(); } @Override public void processOutput(Iterable<T> partialResult) throws IOException, InterruptedException { empty.compareAndSet(true, Iterables.isEmpty(partialResult)); callback.processOutput(partialResult); } @Override public void close(boolean failFast) throws InterruptedException, IOException { callback.close(failFast); } boolean isEmpty() { return empty.get(); } } public QueryExpression transformParsedQuery(QueryExpression queryExpression) { return queryExpression; } public QueryEvalResult evaluateQuery(String query, ThreadSafeOutputFormatterCallback<T> callback) throws QueryException, InterruptedException, IOException { return evaluateQuery( QueryExpression.parse(query, this), callback); } @Override public void reportBuildFileError(QueryExpression caller, String message) throws QueryException { if (!keepGoing) { throw new QueryException(caller, message); } else { // Keep consistent with evaluateQuery() above. eventHandler.handle(Event.error("Evaluation of query \"" + caller + "\" failed: " + message)); } } public abstract Target getTarget(Label label) throws TargetNotFoundException, QueryException, InterruptedException; protected boolean validateScope(Label label, boolean strict) throws QueryException { if (!labelFilter.apply(label)) { String error = String.format("target '%s' is not within the scope of the query", label); if (strict) { throw new QueryException(error); } else { eventHandler.handle(Event.warn(error + ". Skipping")); return false; } } return true; } public QueryTaskFuture<Set<T>> evalTargetPattern(QueryExpression caller, String pattern) { try { preloadOrThrow(caller, ImmutableList.of(pattern)); } catch (TargetParsingException tpe) { try { // Will skip the target and keep going if -k is specified. reportBuildFileError(caller, tpe.getMessage()); } catch (QueryException qe) { return immediateFailedFuture(qe); } } catch (QueryException qe) { return immediateFailedFuture(qe); } catch (InterruptedException e) { return immediateCancelledFuture(); } final AggregateAllCallback<T> aggregatingCallback = QueryUtil.newAggregateAllCallback(); QueryTaskFuture<Void> evalFuture = getTargetsMatchingPattern(caller, pattern, aggregatingCallback); return whenSucceedsCall( evalFuture, new QueryTaskCallable<Set<T>>() { @Override public Set<T> call() { return aggregatingCallback.getResult(); } }); } /** * Perform any work that should be done ahead of time to resolve the target patterns in the query. * Implementations may choose to cache the results of resolving the patterns, cache intermediate * work, or not cache and resolve patterns on the fly. */ protected abstract void preloadOrThrow(QueryExpression caller, Collection<String> patterns) throws QueryException, TargetParsingException, InterruptedException; @Override public boolean isSettingEnabled(Setting setting) { return settings.contains(Preconditions.checkNotNull(setting)); } @Override public Iterable<QueryFunction> getFunctions() { ImmutableList.Builder<QueryFunction> builder = ImmutableList.builder(); builder.addAll(DEFAULT_QUERY_FUNCTIONS); builder.addAll(extraFunctions); return builder.build(); } /** A {@link KeyExtractor} that extracts {@code Label}s out of {@link Target}s. */ protected static class TargetKeyExtractor implements KeyExtractor<Target, Label> { protected static final TargetKeyExtractor INSTANCE = new TargetKeyExtractor(); private TargetKeyExtractor() { } @Override public Label extractKey(Target element) { return element.getLabel(); } } }