blob: 696eee22302e9a85a6b39a54189edb8141744e07 [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 Harmata5bb9cc92016-09-30 21:28:30 +000027import com.google.devtools.build.lib.query2.engine.OutputFormatterCallback;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000028import com.google.devtools.build.lib.query2.engine.QueryEnvironment;
29import com.google.devtools.build.lib.query2.engine.QueryEvalResult;
30import com.google.devtools.build.lib.query2.engine.QueryException;
31import com.google.devtools.build.lib.query2.engine.QueryExpression;
Nathan Harmataf37750a2016-09-07 14:58:14 +000032import com.google.devtools.build.lib.query2.engine.QueryExpressionEvalListener;
Nathan Harmata5bb9cc92016-09-30 21:28:30 +000033import com.google.devtools.build.lib.query2.engine.QueryUtil;
Janak Ramakrishnan0dbc1f92016-01-07 19:12:06 +000034import com.google.devtools.build.lib.query2.engine.QueryUtil.AggregateAllCallback;
Nathan Harmata5bb9cc92016-09-30 21:28:30 +000035import com.google.devtools.build.lib.query2.engine.ThreadSafeCallback;
Nathan Harmatabc47f402016-07-13 16:22:30 +000036import com.google.devtools.build.lib.query2.engine.VariableContext;
Mark Schaller6df81792015-12-10 18:47:47 +000037import com.google.devtools.build.lib.util.Preconditions;
Nathan Harmata5bb9cc92016-09-30 21:28:30 +000038import java.io.IOException;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000039import java.util.Collection;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000040import java.util.LinkedHashSet;
41import java.util.List;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000042import java.util.Set;
Miguel Alcon Pintob6510262015-12-10 17:50:15 +000043import java.util.concurrent.atomic.AtomicBoolean;
Eric Fellheimera39f8a92015-07-28 19:11:23 +000044import java.util.logging.Logger;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000045
46/**
Mark Schaller6cebed62016-06-27 18:05:39 +000047 * {@link QueryEnvironment} that can evaluate queries to produce a result, and implements as much of
48 * QueryEnvironment as possible while remaining mostly agnostic as to the objects being stored.
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000049 */
Mark Schaller6cebed62016-06-27 18:05:39 +000050public abstract class AbstractBlazeQueryEnvironment<T>
Nathan Harmata71616b12016-09-28 20:20:48 +000051 implements QueryEnvironment<T> {
Janak Ramakrishnandf694322016-11-08 18:28:12 +000052 protected ErrorSensingEventHandler eventHandler;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000053 protected final boolean keepGoing;
54 protected final boolean strictScope;
55
Dmitry Lomov6073eb62016-01-21 21:26:32 +000056 protected final DependencyFilter dependencyFilter;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000057 private final Predicate<Label> labelFilter;
58
Janak Ramakrishnandf694322016-11-08 18:28:12 +000059 protected final Set<Setting> settings;
60 protected final List<QueryFunction> extraFunctions;
Nathan Harmataf37750a2016-09-07 14:58:14 +000061 private final QueryExpressionEvalListener<T> evalListener;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +000062
Janak Ramakrishnan6630c252016-11-14 21:28:47 +000063 private static final Logger logger =
64 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);
Janak Ramakrishnan6630c252016-11-14 21:28:47 +0000125 long startTime = System.currentTimeMillis();
126 // 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 {
131 preloadOrThrow(expr, targetPatternSet);
132 } catch (TargetParsingException e) {
133 // Unfortunately, by evaluating the patterns in parallel, we lose some location information.
134 throw new QueryException(expr, e.getMessage());
135 }
136 IOException ioExn = null;
Nathan Harmataa2565aa2016-11-17 22:37:33 +0000137 boolean failFast = true;
Janak Ramakrishnan6630c252016-11-14 21:28:47 +0000138 try {
139 callback.start();
140 evalTopLevelInternal(expr, emptySensingCallback);
Nathan Harmataa2565aa2016-11-17 22:37:33 +0000141 failFast = false;
Janak Ramakrishnan6630c252016-11-14 21:28:47 +0000142 } catch (QueryException e) {
143 throw new QueryException(e, expr);
144 } catch (InterruptedException e) {
145 throw e;
146 } finally {
Nathan Harmatad4803012015-09-08 20:03:22 +0000147 try {
Nathan Harmataa2565aa2016-11-17 22:37:33 +0000148 callback.close(failFast);
Janak Ramakrishnan6630c252016-11-14 21:28:47 +0000149 } catch (IOException e) {
150 // Only throw this IOException if we weren't about to throw a different exception.
151 ioExn = e;
Nathan Harmatad4803012015-09-08 20:03:22 +0000152 }
Janak Ramakrishnan6630c252016-11-14 21:28:47 +0000153 }
154 if (ioExn != null) {
155 throw ioExn;
156 }
157 long elapsedTime = System.currentTimeMillis() - startTime;
158 if (elapsedTime > 1) {
159 logger.info("Spent " + elapsedTime + " milliseconds evaluating query");
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000160 }
161
162 if (eventHandler.hasErrors()) {
163 if (!keepGoing) {
164 // This case represents loading-phase errors reported during evaluation
165 // of target patterns that don't cause evaluation to fail per se.
166 throw new QueryException("Evaluation of query \"" + expr
167 + "\" failed due to BUILD file errors");
168 } else {
169 eventHandler.handle(Event.warn("--keep_going specified, ignoring errors. "
170 + "Results may be inaccurate"));
171 }
172 }
173
Nathan Harmata5bb9cc92016-09-30 21:28:30 +0000174 return new QueryEvalResult(!eventHandler.hasErrors(), emptySensingCallback.isEmpty());
175 }
176
177 private static <T> EmptinessSensingCallback<T> createEmptinessSensingCallback(
178 OutputFormatterCallback<T> callback) {
179 return (callback instanceof ThreadSafeCallback)
180 ? new ThreadSafeEmptinessSensingCallback<>(callback)
181 : new EmptinessSensingCallback<>(callback);
182 }
183
184 private static class EmptinessSensingCallback<T> extends OutputFormatterCallback<T> {
185 private final OutputFormatterCallback<T> callback;
186 private final AtomicBoolean empty = new AtomicBoolean(true);
187
188 private EmptinessSensingCallback(OutputFormatterCallback<T> callback) {
189 this.callback = callback;
190 }
191
192 @Override
193 public void start() throws IOException {
194 callback.start();
195 }
196
197 @Override
198 public void processOutput(Iterable<T> partialResult)
199 throws IOException, InterruptedException {
200 empty.compareAndSet(true, Iterables.isEmpty(partialResult));
201 callback.processOutput(partialResult);
202 }
203
204 @Override
Nathan Harmataa2565aa2016-11-17 22:37:33 +0000205 public void close(boolean failFast) throws InterruptedException, IOException {
206 callback.close(failFast);
Nathan Harmata5bb9cc92016-09-30 21:28:30 +0000207 }
208
209 boolean isEmpty() {
210 return empty.get();
211 }
212 }
213
214 private static class ThreadSafeEmptinessSensingCallback<T>
215 extends EmptinessSensingCallback<T> implements ThreadSafeCallback<T> {
216 private ThreadSafeEmptinessSensingCallback(OutputFormatterCallback<T> callback) {
217 super(callback);
218 Preconditions.checkState(callback instanceof ThreadSafeCallback);
219 }
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000220 }
221
Nathan Harmata2643c8e2016-07-01 23:19:23 +0000222 public QueryExpression transformParsedQuery(QueryExpression queryExpression) {
Nathan Harmataed935602016-03-02 01:16:14 +0000223 return queryExpression;
224 }
225
Nathan Harmata5bb9cc92016-09-30 21:28:30 +0000226 public QueryEvalResult evaluateQuery(String query, OutputFormatterCallback<T> callback)
227 throws QueryException, InterruptedException, IOException {
228 return evaluateQuery(
229 QueryExpression.parse(query, this), callback);
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000230 }
231
232 @Override
233 public void reportBuildFileError(QueryExpression caller, String message) throws QueryException {
234 if (!keepGoing) {
235 throw new QueryException(caller, message);
236 } else {
237 // Keep consistent with evaluateQuery() above.
238 eventHandler.handle(Event.error("Evaluation of query \"" + caller + "\" failed: " + message));
239 }
240 }
241
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000242 public abstract Target getTarget(Label label)
243 throws TargetNotFoundException, QueryException, InterruptedException;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000244
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000245 protected boolean validateScope(Label label, boolean strict) throws QueryException {
246 if (!labelFilter.apply(label)) {
247 String error = String.format("target '%s' is not within the scope of the query", label);
248 if (strict) {
249 throw new QueryException(error);
250 } else {
251 eventHandler.handle(Event.warn(error + ". Skipping"));
252 return false;
253 }
254 }
255 return true;
256 }
257
258 public Set<T> evalTargetPattern(QueryExpression caller, String pattern)
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000259 throws QueryException, InterruptedException {
Janak Ramakrishnanee6208b2016-01-07 20:17:41 +0000260 try {
261 preloadOrThrow(caller, ImmutableList.of(pattern));
262 } catch (TargetParsingException e) {
263 // Will skip the target and keep going if -k is specified.
264 reportBuildFileError(caller, e.getMessage());
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000265 }
Nathan Harmata5bb9cc92016-09-30 21:28:30 +0000266 AggregateAllCallback<T> aggregatingCallback = QueryUtil.newAggregateAllCallback();
Janak Ramakrishnan0dbc1f92016-01-07 19:12:06 +0000267 getTargetsMatchingPattern(caller, pattern, aggregatingCallback);
268 return aggregatingCallback.getResult();
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000269 }
270
Janak Ramakrishnanee6208b2016-01-07 20:17:41 +0000271 /**
Janak Ramakrishnan71cdea42016-08-16 15:14:41 +0000272 * Perform any work that should be done ahead of time to resolve the target patterns in the query.
273 * Implementations may choose to cache the results of resolving the patterns, cache intermediate
274 * work, or not cache and resolve patterns on the fly.
Janak Ramakrishnanee6208b2016-01-07 20:17:41 +0000275 */
276 protected abstract void preloadOrThrow(QueryExpression caller, Collection<String> patterns)
Janak Ramakrishnan71cdea42016-08-16 15:14:41 +0000277 throws QueryException, TargetParsingException, InterruptedException;
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000278
279 @Override
280 public boolean isSettingEnabled(Setting setting) {
281 return settings.contains(Preconditions.checkNotNull(setting));
282 }
283
284 @Override
285 public Iterable<QueryFunction> getFunctions() {
286 ImmutableList.Builder<QueryFunction> builder = ImmutableList.builder();
287 builder.addAll(DEFAULT_QUERY_FUNCTIONS);
288 builder.addAll(extraFunctions);
289 return builder.build();
290 }
Nathan Harmataf37750a2016-09-07 14:58:14 +0000291
292 @Override
293 public QueryExpressionEvalListener<T> getEvalListener() {
294 return evalListener;
295 }
Janak Ramakrishnana46125c2015-02-11 16:51:37 +0000296}