blob: 98ab05583ea0374861f51ed5fe8918c861e18265 [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
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.syntax;
15
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +000016import com.google.common.annotations.VisibleForTesting;
Laurent Le Brun8e965b82016-08-03 11:50:24 +000017import com.google.common.base.Joiner;
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +000018import com.google.common.base.Preconditions;
19import com.google.common.collect.ImmutableBiMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010020import com.google.common.collect.ImmutableList;
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +000021import com.google.common.collect.ImmutableMap;
Lukacs Berki48338222015-06-12 11:37:46 +000022import com.google.common.hash.HashCode;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010023import com.google.devtools.build.lib.events.Event;
24import com.google.devtools.build.lib.events.EventHandler;
25import com.google.devtools.build.lib.events.Location;
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +000026import com.google.devtools.build.lib.syntax.Parser.ParseResult;
27import com.google.devtools.build.lib.syntax.SkylarkImports.SkylarkImportSyntaxException;
28import com.google.devtools.build.lib.util.Pair;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010029import com.google.devtools.build.lib.vfs.Path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030import java.io.IOException;
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +000031import java.util.HashMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010032import java.util.List;
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +000033import java.util.Map;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010034import javax.annotation.Nullable;
35
36/**
37 * Abstract syntax node for an entire BUILD file.
38 */
39public class BuildFileAST extends ASTNode {
40
41 private final ImmutableList<Statement> stmts;
42
43 private final ImmutableList<Comment> comments;
44
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +000045 @Nullable private final Map<String, SkylarkImport> imports;
Laurent Le Brun6190ffb2015-07-01 16:24:56 +000046
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010047 /**
48 * Whether any errors were encountered during scanning or parsing.
49 */
50 private final boolean containsErrors;
51
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +000052 @Nullable private final String contentHashCode;
Lukacs Berkif445ea12015-07-09 07:16:41 +000053
Damien Martin-Guillerez5e95a462016-02-05 22:32:08 +000054 private BuildFileAST(
55 ImmutableList<Statement> stmts,
56 boolean containsErrors,
57 String contentHashCode,
Laurent Le Brun8c8857d2016-08-04 10:22:16 +000058 Location location,
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +000059 ImmutableList<Comment> comments,
60 @Nullable Map<String, SkylarkImport> imports) {
Damien Martin-Guillerez5e95a462016-02-05 22:32:08 +000061 this.stmts = stmts;
62 this.containsErrors = containsErrors;
63 this.contentHashCode = contentHashCode;
Laurent Le Brun8c8857d2016-08-04 10:22:16 +000064 this.comments = comments;
Damien Martin-Guillerez5e95a462016-02-05 22:32:08 +000065 this.setLocation(location);
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +000066 this.imports = imports;
67 }
68
69 private static BuildFileAST create(
70 List<Statement> preludeStatements,
71 ParseResult result,
72 String contentHashCode,
73 EventHandler eventHandler) {
74 ImmutableList<Statement> stmts =
75 ImmutableList.<Statement>builder()
76 .addAll(preludeStatements)
77 .addAll(result.statements)
78 .build();
79
80 boolean containsErrors = result.containsErrors;
81 Pair<Boolean, Map<String, SkylarkImport>> skylarkImports = fetchLoads(stmts, eventHandler);
82 containsErrors |= skylarkImports.first;
83 return new BuildFileAST(
84 stmts,
85 containsErrors,
86 contentHashCode,
87 result.location,
88 ImmutableList.copyOf(result.comments),
89 skylarkImports.second);
Damien Martin-Guillerez5e95a462016-02-05 22:32:08 +000090 }
91
92 /**
93 * Extract a subtree containing only statements from {@code firstStatement} (included) up to
94 * {@code lastStatement} excluded.
95 */
96 public BuildFileAST subTree(int firstStatement, int lastStatement) {
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +000097 ImmutableList<Statement> stmts = this.stmts.subList(firstStatement, lastStatement);
98 ImmutableMap.Builder<String, SkylarkImport> imports = ImmutableBiMap.builder();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010099 for (Statement stmt : stmts) {
100 if (stmt instanceof LoadStatement) {
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +0000101 String str = ((LoadStatement) stmt).getImport();
102 imports.put(
103 str,
104 Preconditions.checkNotNull(
105 this.imports.get(str), "%s cannot be found. This is an internal error.", str));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100106 }
107 }
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +0000108 return new BuildFileAST(
109 stmts,
110 containsErrors,
111 null,
112 this.stmts.get(firstStatement).getLocation(),
113 ImmutableList.<Comment>of(),
114 imports.build());
115 }
116
117 /**
118 * Collects all load statements. Returns a pair with a boolean saying if there were errors and the
119 * imports that could be resolved.
120 */
121 @VisibleForTesting
122 static Pair<Boolean, Map<String, SkylarkImport>> fetchLoads(
123 List<Statement> stmts, EventHandler eventHandler) {
124 Map<String, SkylarkImport> imports = new HashMap<>();
125 boolean error = false;
126 for (Statement stmt : stmts) {
127 if (stmt instanceof LoadStatement) {
128 String importString = ((LoadStatement) stmt).getImport();
129 try {
130 SkylarkImport imp = SkylarkImports.create(importString);
131 imports.put(importString, imp);
132 } catch (SkylarkImportSyntaxException e) {
133 eventHandler.handle(Event.error(stmt.getLocation(), e.getMessage()));
134 error = true;
135 }
136 }
137 }
138 return Pair.of(error, imports);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100139 }
140
141 /**
142 * Returns true if any errors were encountered during scanning or parsing. If
143 * set, clients should not rely on the correctness of the AST for builds or
144 * BUILD-file editing.
145 */
146 public boolean containsErrors() {
147 return containsErrors;
148 }
149
150 /**
151 * Returns an (immutable, ordered) list of statements in this BUILD file.
152 */
153 public ImmutableList<Statement> getStatements() {
154 return stmts;
155 }
156
157 /**
158 * Returns an (immutable, ordered) list of comments in this BUILD file.
159 */
160 public ImmutableList<Comment> getComments() {
161 return comments;
162 }
163
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +0000164 /** Returns a list of loads in this BUILD file. */
165 public ImmutableList<SkylarkImport> getImports() {
166 Preconditions.checkNotNull(imports, "computeImports Should be called in parse* methods");
167 return ImmutableList.copyOf(imports.values());
Laurent Le Brun6190ffb2015-07-01 16:24:56 +0000168 }
169
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +0000170 /** Returns a list of loads as strings in this BUILD file. */
171 public synchronized ImmutableList<String> getRawImports() {
172 ImmutableList.Builder<String> imports = ImmutableList.builder();
173
174 for (Statement stmt : stmts) {
175 if (stmt instanceof LoadStatement) {
176 imports.add(((LoadStatement) stmt).getImport());
177 }
178 }
179 return imports.build();
180 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100181 /**
182 * Executes this build file in a given Environment.
183 *
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +0000184 * <p>If, for any reason, execution of a statement cannot be completed, an {@link EvalException}
185 * is thrown by {@link Statement#exec(Environment)}. This exception is caught here and reported
186 * through reporter and execution continues on the next statement. In effect, there is a
187 * "try/except" block around every top level statement. Such exceptions are not ignored, though:
188 * they are visible via the return value. Rules declared in a package containing any error
189 * (including loading-phase semantical errors that cannot be checked here) must also be considered
190 * "in error".
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100191 *
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +0000192 * <p>Note that this method will not affect the value of {@link #containsErrors()}; that refers
193 * only to lexer/parser errors.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100194 *
195 * @return true if no error occurred during execution.
196 */
197 public boolean exec(Environment env, EventHandler eventHandler) throws InterruptedException {
198 boolean ok = true;
199 for (Statement stmt : stmts) {
200 try {
201 stmt.exec(env);
202 } catch (EvalException e) {
203 ok = false;
204 // Do not report errors caused by a previous parsing error, as it has already been
205 // reported.
206 if (e.isDueToIncompleteAST()) {
207 continue;
208 }
209 // When the exception is raised from another file, report first the location in the
210 // BUILD file (as it is the most probable cause for the error).
211 Location exnLoc = e.getLocation();
212 Location nodeLoc = stmt.getLocation();
Florian Weikert3f610e82015-08-18 14:37:46 +0000213 eventHandler.handle(Event.error(
214 (exnLoc == null || !nodeLoc.getPath().equals(exnLoc.getPath())) ? nodeLoc : exnLoc,
215 e.getMessage()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100216 }
217 }
218 return ok;
219 }
220
221 @Override
222 public String toString() {
223 return "BuildFileAST" + getStatements();
224 }
225
226 @Override
227 public void accept(SyntaxTreeVisitor visitor) {
228 visitor.visit(this);
229 }
230
231 /**
232 * Parse the specified build file, returning its AST. All errors during
233 * scanning or parsing will be reported to the reporter.
234 *
235 * @throws IOException if the file cannot not be read.
236 */
Laurent Le Brunf1112b32016-08-03 13:16:02 +0000237 public static BuildFileAST parseBuildFile(Path buildFile, EventHandler eventHandler)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100238 throws IOException {
Laurent Le Brunf1112b32016-08-03 13:16:02 +0000239 return parseBuildFile(buildFile, buildFile.getFileSize(), eventHandler);
Nathan Harmata7f240222015-09-13 02:03:26 +0000240 }
241
242 public static BuildFileAST parseBuildFile(Path buildFile, long fileSize,
Laurent Le Brunf1112b32016-08-03 13:16:02 +0000243 EventHandler eventHandler)
Nathan Harmata7f240222015-09-13 02:03:26 +0000244 throws IOException {
245 ParserInputSource inputSource = ParserInputSource.create(buildFile, fileSize);
Laurent Le Brunf1112b32016-08-03 13:16:02 +0000246 return parseBuildFile(inputSource, eventHandler);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100247 }
248
249 /**
250 * Parse the specified build file, returning its AST. All errors during
251 * scanning or parsing will be reported to the reporter.
252 */
253 public static BuildFileAST parseBuildFile(ParserInputSource input,
254 List<Statement> preludeStatements,
Laurent Le Brunf1112b32016-08-03 13:16:02 +0000255 EventHandler eventHandler) {
256 Parser.ParseResult result = Parser.parseFile(input, eventHandler, false);
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +0000257 return create(preludeStatements, result, /*contentHashCode=*/ null, eventHandler);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100258 }
259
Laurent Le Brunf1112b32016-08-03 13:16:02 +0000260 public static BuildFileAST parseBuildFile(ParserInputSource input, EventHandler eventHandler) {
261 Parser.ParseResult result = Parser.parseFile(input, eventHandler, false);
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +0000262 return create(ImmutableList.<Statement>of(), result, /*contentHashCode=*/ null, eventHandler);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100263 }
264
265 /**
Laurent Le Brun8c8857d2016-08-04 10:22:16 +0000266 * Parse the specified Skylark file, returning its AST. All errors during scanning or parsing will
267 * be reported to the reporter.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100268 *
269 * @throws IOException if the file cannot not be read.
270 */
Laurent Le Brun8c8857d2016-08-04 10:22:16 +0000271 public static BuildFileAST parseSkylarkFile(Path file, EventHandler eventHandler)
272 throws IOException {
273 return parseSkylarkFile(file, file.getFileSize(), eventHandler);
Nathan Harmata7f240222015-09-13 02:03:26 +0000274 }
275
Laurent Le Brun8c8857d2016-08-04 10:22:16 +0000276 public static BuildFileAST parseSkylarkFile(Path file, long fileSize, EventHandler eventHandler)
277 throws IOException {
Nathan Harmata7f240222015-09-13 02:03:26 +0000278 ParserInputSource input = ParserInputSource.create(file, fileSize);
Laurent Le Brun8c8857d2016-08-04 10:22:16 +0000279 Parser.ParseResult result = Parser.parseFileForSkylark(input, eventHandler);
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +0000280 return create(
281 ImmutableList.<Statement>of(), result,
282 HashCode.fromBytes(file.getMD5Digest()).toString(), eventHandler);
283 }
284
285 /**
286 * Parse the specified non-build Skylark file but avoid the validation of the imports, returning
287 * its AST. All errors during scanning or parsing will be reported to the reporter.
288 *
289 * <p>This method should not be used in Bazel code, since it doesn't validate that the imports are
290 * syntactically valid.
291 *
292 * @throws IOException if the file cannot not be read.
293 */
294 public static BuildFileAST parseSkylarkFileWithoutImports(
295 ParserInputSource input, EventHandler eventHandler) throws IOException {
296 ParseResult result = Parser.parseFileForSkylark(input, eventHandler);
297 return new BuildFileAST(
298 ImmutableList.<Statement>builder()
299 .addAll(ImmutableList.<Statement>of())
300 .addAll(result.statements)
301 .build(),
302 result.containsErrors, /*contentHashCode=*/
303 null,
304 result.location,
305 ImmutableList.copyOf(result.comments), /*imports=*/
306 null);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100307 }
308
Laurent Le Brun8c8857d2016-08-04 10:22:16 +0000309 /**
310 * Run static checks on the AST.
311 *
312 * @return a new AST (or the same), with the containsErrors flag updated.
313 */
314 public BuildFileAST validate(ValidationEnvironment validationEnv, EventHandler eventHandler) {
315 boolean valid = validationEnv.validateAst(stmts, eventHandler);
316 if (valid || containsErrors) {
317 return this;
318 }
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +0000319 return new BuildFileAST(stmts, true, contentHashCode, getLocation(), comments, imports);
Laurent Le Brun8c8857d2016-08-04 10:22:16 +0000320 }
321
Laurent Le Brun8e965b82016-08-03 11:50:24 +0000322 public static BuildFileAST parseBuildString(EventHandler eventHandler, String... content) {
323 String str = Joiner.on("\n").join(content);
324 ParserInputSource input = ParserInputSource.create(str, null);
325 Parser.ParseResult result = Parser.parseFile(input, eventHandler, false);
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +0000326 return create(ImmutableList.<Statement>of(), result, null, eventHandler);
Laurent Le Brun8e965b82016-08-03 11:50:24 +0000327 }
328
329 // TODO(laurentlb): Merge parseSkylarkString and parseBuildString.
330 public static BuildFileAST parseSkylarkString(EventHandler eventHandler, String... content) {
331 String str = Joiner.on("\n").join(content);
332 ParserInputSource input = ParserInputSource.create(str, null);
Laurent Le Brun8c8857d2016-08-04 10:22:16 +0000333 Parser.ParseResult result = Parser.parseFileForSkylark(input, eventHandler);
Miguel Alcon Pinto927f3b22016-08-22 14:21:30 +0000334 return create(ImmutableList.<Statement>of(), result, null, eventHandler);
Laurent Le Brun8e965b82016-08-03 11:50:24 +0000335 }
336
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100337 /**
338 * Parse the specified build file, without building the AST.
339 *
340 * @return true if the input file is syntactically valid
341 */
Laurent Le Brunf1112b32016-08-03 13:16:02 +0000342 public static boolean checkSyntax(
343 ParserInputSource input, EventHandler eventHandler, boolean parsePython) {
344 Parser.ParseResult result = Parser.parseFile(input, eventHandler, parsePython);
345 return !result.containsErrors;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100346 }
347
348 /**
Laurent Le Brun8e965b82016-08-03 11:50:24 +0000349 * Evaluates the code and return the value of the last statement if it's an
350 * Expression or else null.
351 */
352 @Nullable public Object eval(Environment env) throws EvalException, InterruptedException {
353 Object last = null;
354 for (Statement statement : stmts) {
355 if (statement instanceof ExpressionStatement) {
356 last = ((ExpressionStatement) statement).getExpression().eval(env);
357 } else {
358 statement.exec(env);
359 last = null;
360 }
361 }
362 return last;
363 }
364
365 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100366 * Returns a hash code calculated from the string content of the source file of this AST.
367 */
368 @Nullable public String getContentHashCode() {
369 return contentHashCode;
370 }
371}