blob: 32d8617f8b8faef7ec413ac599b56dc209727c66 [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2015 The Bazel Authors. All rights reserved.
Janak Ramakrishnana46125c2015-02-11 16:51:37 +00002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.lib.query2;
15
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000016import com.google.common.base.Predicate;
17import com.google.common.collect.ImmutableList;
Miguel Alcon Pintob6510262015-12-10 17:50:15 +000018import com.google.common.collect.Iterables;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000019import com.google.common.collect.Sets;
Lukacs Berki6e91eb92015-09-21 09:12:37 +000020import com.google.devtools.build.lib.cmdline.Label;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000021import com.google.devtools.build.lib.cmdline.TargetParsingException;
22import com.google.devtools.build.lib.events.ErrorSensingEventHandler;
23import com.google.devtools.build.lib.events.Event;
24import com.google.devtools.build.lib.events.EventHandler;
Dmitry Lomov6073eb62016-01-21 21:26:32 +000025import com.google.devtools.build.lib.packages.DependencyFilter;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000026import com.google.devtools.build.lib.packages.Target;
Nathan Harmatad4803012015-09-08 20:03:22 +000027import com.google.devtools.build.lib.profiler.AutoProfiler;
Nathan Harmata5bb9cc92016-09-30 21:28:30 +000028import com.google.devtools.build.lib.query2.engine.OutputFormatterCallback;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000029import com.google.devtools.build.lib.query2.engine.QueryEnvironment;
30import com.google.devtools.build.lib.query2.engine.QueryEvalResult;
31import com.google.devtools.build.lib.query2.engine.QueryException;
32import com.google.devtools.build.lib.query2.engine.QueryExpression;
Nathan Harmataf37750a2016-09-07 14:58:14 +000033import com.google.devtools.build.lib.query2.engine.QueryExpressionEvalListener;
Nathan Harmata5bb9cc92016-09-30 21:28:30 +000034import com.google.devtools.build.lib.query2.engine.QueryUtil;
Janak Ramakrishnan0dbc1f92016-01-07 19:12:06 +000035import com.google.devtools.build.lib.query2.engine.QueryUtil.AggregateAllCallback;
Nathan Harmata5bb9cc92016-09-30 21:28:30 +000036import com.google.devtools.build.lib.query2.engine.ThreadSafeCallback;
Nathan Harmatabc47f402016-07-13 16:22:30 +000037import com.google.devtools.build.lib.query2.engine.VariableContext;
Mark Schaller6df81792015-12-10 18:47:47 +000038import com.google.devtools.build.lib.util.Preconditions;
Nathan Harmata5bb9cc92016-09-30 21:28:30 +000039import java.io.IOException;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000040import java.util.Collection;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000041import java.util.LinkedHashSet;
42import java.util.List;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000043import java.util.Set;
Miguel Alcon Pintob6510262015-12-10 17:50:15 +000044import java.util.concurrent.atomic.AtomicBoolean;
Eric Fellheimera39f8a92015-07-28 19:11:23 +000045import java.util.logging.Logger;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000046
47/**
Mark Schaller6cebed62016-06-27 18:05:39 +000048 * {@link QueryEnvironment} that can evaluate queries to produce a result, and implements as much of
49 * QueryEnvironment as possible while remaining mostly agnostic as to the objects being stored.
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000050 */
Mark Schaller6cebed62016-06-27 18:05:39 +000051public abstract class AbstractBlazeQueryEnvironment<T>
Nathan Harmata71616b12016-09-28 20:20:48 +000052 implements QueryEnvironment<T> {
Janak Ramakrishnandf694322016-11-08 18:28:12 +000053 protected ErrorSensingEventHandler eventHandler;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000054 protected final boolean keepGoing;
55 protected final boolean strictScope;
56
Dmitry Lomov6073eb62016-01-21 21:26:32 +000057 protected final DependencyFilter dependencyFilter;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000058 private final Predicate<Label> labelFilter;
59
Janak Ramakrishnandf694322016-11-08 18:28:12 +000060 protected final Set<Setting> settings;
61 protected final List<QueryFunction> extraFunctions;
Nathan Harmataf37750a2016-09-07 14:58:14 +000062 private final QueryExpressionEvalListener<T> evalListener;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000063
Nathan Harmataf0cc5b82016-03-18 14:56:28 +000064 private static final Logger LOG = Logger.getLogger(AbstractBlazeQueryEnvironment.class.getName());
Eric Fellheimera39f8a92015-07-28 19:11:23 +000065
Janak Ramakrishnane72d5222015-02-26 17:09:18 +000066 protected AbstractBlazeQueryEnvironment(boolean keepGoing,
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000067 boolean strictScope,
68 Predicate<Label> labelFilter,
69 EventHandler eventHandler,
70 Set<Setting> settings,
Nathan Harmataf37750a2016-09-07 14:58:14 +000071 Iterable<QueryFunction> extraFunctions,
72 QueryExpressionEvalListener<T> evalListener) {
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000073 this.eventHandler = new ErrorSensingEventHandler(eventHandler);
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000074 this.keepGoing = keepGoing;
75 this.strictScope = strictScope;
76 this.dependencyFilter = constructDependencyFilter(settings);
77 this.labelFilter = labelFilter;
78 this.settings = Sets.immutableEnumSet(settings);
79 this.extraFunctions = ImmutableList.copyOf(extraFunctions);
Nathan Harmataf37750a2016-09-07 14:58:14 +000080 this.evalListener = evalListener;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000081 }
82
Dmitry Lomovcb14c5e2016-01-21 22:04:01 +000083 private static DependencyFilter constructDependencyFilter(
84 Set<Setting> settings) {
Dmitry Lomov6073eb62016-01-21 21:26:32 +000085 DependencyFilter specifiedFilter =
86 settings.contains(Setting.NO_HOST_DEPS)
87 ? DependencyFilter.NO_HOST_DEPS
88 : DependencyFilter.ALL_DEPS;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000089 if (settings.contains(Setting.NO_IMPLICIT_DEPS)) {
Dmitry Lomov6073eb62016-01-21 21:26:32 +000090 specifiedFilter = DependencyFilter.and(specifiedFilter, DependencyFilter.NO_IMPLICIT_DEPS);
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000091 }
92 if (settings.contains(Setting.NO_NODEP_DEPS)) {
Dmitry Lomov6073eb62016-01-21 21:26:32 +000093 specifiedFilter = DependencyFilter.and(specifiedFilter, DependencyFilter.NO_NODEP_ATTRIBUTES);
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000094 }
95 return specifiedFilter;
96 }
97
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000098 /**
Nathan Harmata5bb9cc92016-09-30 21:28:30 +000099 * Used by {@link #evaluateQuery} to evaluate the given {@code expr}. The caller,
100 * {@link #evaluateQuery}, not {@link #evalTopLevelInternal}, is responsible for managing
101 * {@code callback}.
102 */
103 protected void evalTopLevelInternal(QueryExpression expr, OutputFormatterCallback<T> callback)
104 throws QueryException, InterruptedException {
105 eval(expr, VariableContext.<T>empty(), callback);
106 }
107
108 /**
109 * Evaluate the specified query expression in this environment, streaming results to the given
110 * {@code callback}. {@code callback.start()} will be called before query evaluation and
111 * {@code callback.close()} will be unconditionally called at the end of query evaluation
112 * (i.e. regardless of whether it was successful).
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000113 *
114 * @return a {@link QueryEvalResult} object that contains the resulting set of targets and a bit
Mark Schaller895b3d22015-03-23 14:27:26 +0000115 * to indicate whether errors occurred during evaluation; note that the
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000116 * success status can only be false if {@code --keep_going} was in effect
117 * @throws QueryException if the evaluation failed and {@code --nokeep_going} was in
118 * effect
119 */
Nathan Harmata5bb9cc92016-09-30 21:28:30 +0000120 public QueryEvalResult evaluateQuery(
121 QueryExpression expr,
122 final OutputFormatterCallback<T> callback)
123 throws QueryException, InterruptedException, IOException {
124 EmptinessSensingCallback<T> emptySensingCallback = createEmptinessSensingCallback(callback);
Miguel Alcon Pintob6510262015-12-10 17:50:15 +0000125 try (final AutoProfiler p = AutoProfiler.logged("evaluating query", LOG)) {
Nathan Harmatad4803012015-09-08 20:03:22 +0000126 // In the --nokeep_going case, errors are reported in the order in which the patterns are
127 // specified; using a linked hash set here makes sure that the left-most error is reported.
128 Set<String> targetPatternSet = new LinkedHashSet<>();
129 expr.collectTargetPatterns(targetPatternSet);
130 try {
Janak Ramakrishnanee6208b2016-01-07 20:17:41 +0000131 preloadOrThrow(expr, targetPatternSet);
Nathan Harmatad4803012015-09-08 20:03:22 +0000132 } catch (TargetParsingException e) {
133 // Unfortunately, by evaluating the patterns in parallel, we lose some location information.
134 throw new QueryException(expr, e.getMessage());
135 }
Nathan Harmata5bb9cc92016-09-30 21:28:30 +0000136 IOException ioExn = null;
Nathan Harmatad4803012015-09-08 20:03:22 +0000137 try {
Nathan Harmata5bb9cc92016-09-30 21:28:30 +0000138 callback.start();
139 evalTopLevelInternal(expr, emptySensingCallback);
Nathan Harmatad4803012015-09-08 20:03:22 +0000140 } catch (QueryException e) {
141 throw new QueryException(e, expr);
Nathan Harmata5bb9cc92016-09-30 21:28:30 +0000142 } catch (InterruptedException e) {
143 throw e;
144 } finally {
145 try {
146 callback.close();
147 } catch (IOException e) {
148 // Only throw this IOException if we weren't about to throw a different exception.
149 ioExn = e;
150 }
151 }
152 if (ioExn != null) {
153 throw ioExn;
Eric Fellheimera39f8a92015-07-28 19:11:23 +0000154 }
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000155 }
156
157 if (eventHandler.hasErrors()) {
158 if (!keepGoing) {
159 // This case represents loading-phase errors reported during evaluation
160 // of target patterns that don't cause evaluation to fail per se.
161 throw new QueryException("Evaluation of query \"" + expr
162 + "\" failed due to BUILD file errors");
163 } else {
164 eventHandler.handle(Event.warn("--keep_going specified, ignoring errors. "
165 + "Results may be inaccurate"));
166 }
167 }
168
Nathan Harmata5bb9cc92016-09-30 21:28:30 +0000169 return new QueryEvalResult(!eventHandler.hasErrors(), emptySensingCallback.isEmpty());
170 }
171
172 private static <T> EmptinessSensingCallback<T> createEmptinessSensingCallback(
173 OutputFormatterCallback<T> callback) {
174 return (callback instanceof ThreadSafeCallback)
175 ? new ThreadSafeEmptinessSensingCallback<>(callback)
176 : new EmptinessSensingCallback<>(callback);
177 }
178
179 private static class EmptinessSensingCallback<T> extends OutputFormatterCallback<T> {
180 private final OutputFormatterCallback<T> callback;
181 private final AtomicBoolean empty = new AtomicBoolean(true);
182
183 private EmptinessSensingCallback(OutputFormatterCallback<T> callback) {
184 this.callback = callback;
185 }
186
187 @Override
188 public void start() throws IOException {
189 callback.start();
190 }
191
192 @Override
193 public void processOutput(Iterable<T> partialResult)
194 throws IOException, InterruptedException {
195 empty.compareAndSet(true, Iterables.isEmpty(partialResult));
196 callback.processOutput(partialResult);
197 }
198
199 @Override
200 public void close() throws InterruptedException, IOException {
201 callback.close();
202 }
203
204 boolean isEmpty() {
205 return empty.get();
206 }
207 }
208
209 private static class ThreadSafeEmptinessSensingCallback<T>
210 extends EmptinessSensingCallback<T> implements ThreadSafeCallback<T> {
211 private ThreadSafeEmptinessSensingCallback(OutputFormatterCallback<T> callback) {
212 super(callback);
213 Preconditions.checkState(callback instanceof ThreadSafeCallback);
214 }
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000215 }
216
Nathan Harmata2643c8e2016-07-01 23:19:23 +0000217 public QueryExpression transformParsedQuery(QueryExpression queryExpression) {
Nathan Harmataed935602016-03-02 01:16:14 +0000218 return queryExpression;
219 }
220
Nathan Harmata5bb9cc92016-09-30 21:28:30 +0000221 public QueryEvalResult evaluateQuery(String query, OutputFormatterCallback<T> callback)
222 throws QueryException, InterruptedException, IOException {
223 return evaluateQuery(
224 QueryExpression.parse(query, this), callback);
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000225 }
226
227 @Override
228 public void reportBuildFileError(QueryExpression caller, String message) throws QueryException {
229 if (!keepGoing) {
230 throw new QueryException(caller, message);
231 } else {
232 // Keep consistent with evaluateQuery() above.
233 eventHandler.handle(Event.error("Evaluation of query \"" + caller + "\" failed: " + message));
234 }
235 }
236
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000237 public abstract Target getTarget(Label label)
238 throws TargetNotFoundException, QueryException, InterruptedException;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000239
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000240 protected boolean validateScope(Label label, boolean strict) throws QueryException {
241 if (!labelFilter.apply(label)) {
242 String error = String.format("target '%s' is not within the scope of the query", label);
243 if (strict) {
244 throw new QueryException(error);
245 } else {
246 eventHandler.handle(Event.warn(error + ". Skipping"));
247 return false;
248 }
249 }
250 return true;
251 }
252
253 public Set<T> evalTargetPattern(QueryExpression caller, String pattern)
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000254 throws QueryException, InterruptedException {
Janak Ramakrishnanee6208b2016-01-07 20:17:41 +0000255 try {
256 preloadOrThrow(caller, ImmutableList.of(pattern));
257 } catch (TargetParsingException e) {
258 // Will skip the target and keep going if -k is specified.
259 reportBuildFileError(caller, e.getMessage());
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000260 }
Nathan Harmata5bb9cc92016-09-30 21:28:30 +0000261 AggregateAllCallback<T> aggregatingCallback = QueryUtil.newAggregateAllCallback();
Janak Ramakrishnan0dbc1f92016-01-07 19:12:06 +0000262 getTargetsMatchingPattern(caller, pattern, aggregatingCallback);
263 return aggregatingCallback.getResult();
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000264 }
265
Janak Ramakrishnanee6208b2016-01-07 20:17:41 +0000266 /**
Janak Ramakrishnan71cdea42016-08-16 15:14:41 +0000267 * Perform any work that should be done ahead of time to resolve the target patterns in the query.
268 * Implementations may choose to cache the results of resolving the patterns, cache intermediate
269 * work, or not cache and resolve patterns on the fly.
Janak Ramakrishnanee6208b2016-01-07 20:17:41 +0000270 */
271 protected abstract void preloadOrThrow(QueryExpression caller, Collection<String> patterns)
Janak Ramakrishnan71cdea42016-08-16 15:14:41 +0000272 throws QueryException, TargetParsingException, InterruptedException;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000273
274 @Override
275 public boolean isSettingEnabled(Setting setting) {
276 return settings.contains(Preconditions.checkNotNull(setting));
277 }
278
279 @Override
280 public Iterable<QueryFunction> getFunctions() {
281 ImmutableList.Builder<QueryFunction> builder = ImmutableList.builder();
282 builder.addAll(DEFAULT_QUERY_FUNCTIONS);
283 builder.addAll(extraFunctions);
284 return builder.build();
285 }
Nathan Harmataf37750a2016-09-07 14:58:14 +0000286
287 @Override
288 public QueryExpressionEvalListener<T> getEvalListener() {
289 return evalListener;
290 }
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000291}