| // Copyright 2019 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.bazel.rules.ninja.parser; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.google.devtools.build.lib.bazel.rules.ninja.file.FileFragment; |
| import com.google.devtools.build.lib.bazel.rules.ninja.file.GenericParsingException; |
| import com.google.devtools.build.lib.util.Pair; |
| import java.io.IOException; |
| import java.util.Collection; |
| import java.util.Comparator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.NavigableMap; |
| import java.util.TreeMap; |
| |
| /** |
| * Class to hold information about different declarations in Ninja file during parsing. |
| * |
| * <p>Included files (with include or subninja statements) are kept in form of promises: {@link |
| * NinjaPromise<NinjaFileParseResult>}, since for their parsing we may need to first resolve the |
| * variables in the current file. |
| */ |
| public class NinjaFileParseResult { |
| /** |
| * Interface for getting the result of lazy parsing of Ninja file in the context of {@link |
| * NinjaScope}. |
| * |
| * @param <T> result of parsing. |
| */ |
| public interface NinjaPromise<T> { |
| T compute(NinjaScope scope) throws GenericParsingException, InterruptedException, IOException; |
| } |
| |
| /** Interface for getting result of lazy parsing of Ninja declaration. */ |
| public interface NinjaCallable { |
| void call() throws GenericParsingException, InterruptedException, IOException; |
| } |
| |
| private final NavigableMap<String, List<Pair<Long, NinjaVariableValue>>> variables; |
| private final NavigableMap<String, List<Pair<Long, NinjaRule>>> rules; |
| private final NavigableMap<String, List<Pair<Long, NinjaPool>>> pools; |
| private final List<FileFragment> targets; |
| private final NavigableMap<Long, NinjaPromise<NinjaFileParseResult>> includedFilesFutures; |
| private final NavigableMap<Long, NinjaPromise<NinjaFileParseResult>> subNinjaFilesFutures; |
| |
| public NinjaFileParseResult() { |
| variables = Maps.newTreeMap(); |
| rules = Maps.newTreeMap(); |
| pools = Maps.newTreeMap(); |
| targets = Lists.newArrayList(); |
| includedFilesFutures = Maps.newTreeMap(); |
| subNinjaFilesFutures = Maps.newTreeMap(); |
| } |
| |
| public void addIncludeScope(long offset, NinjaPromise<NinjaFileParseResult> promise) { |
| includedFilesFutures.put(offset, promise); |
| } |
| |
| public void addSubNinjaScope(long offset, NinjaPromise<NinjaFileParseResult> promise) { |
| subNinjaFilesFutures.put(offset, promise); |
| } |
| |
| public void addTarget(FileFragment fragment) { |
| targets.add(fragment); |
| } |
| |
| public void addVariable(String name, long offset, NinjaVariableValue value) { |
| variables.computeIfAbsent(name, k -> Lists.newArrayList()).add(Pair.of(offset, value)); |
| } |
| |
| public void addRule(long offset, NinjaRule rule) { |
| rules.computeIfAbsent(rule.getName(), k -> Lists.newArrayList()).add(Pair.of(offset, rule)); |
| } |
| |
| public void addPool(long offset, NinjaPool pool) { |
| pools.computeIfAbsent(pool.getName(), k -> Lists.newArrayList()).add(Pair.of(offset, pool)); |
| } |
| |
| @VisibleForTesting |
| public Map<String, List<Pair<Long, NinjaVariableValue>>> getVariables() { |
| return variables; |
| } |
| |
| @VisibleForTesting |
| public Map<String, List<Pair<Long, NinjaRule>>> getRules() { |
| return rules; |
| } |
| |
| public List<FileFragment> getTargets() { |
| return targets; |
| } |
| |
| @VisibleForTesting |
| public void sortResults() { |
| for (List<Pair<Long, NinjaVariableValue>> list : variables.values()) { |
| list.sort(Comparator.comparing(Pair::getFirst)); |
| } |
| for (List<Pair<Long, NinjaRule>> list : rules.values()) { |
| list.sort(Comparator.comparing(Pair::getFirst)); |
| } |
| for (List<Pair<Long, NinjaPool>> list : pools.values()) { |
| list.sort(Comparator.comparing(Pair::getFirst)); |
| } |
| } |
| |
| public static NinjaFileParseResult merge(Collection<NinjaFileParseResult> parts) { |
| NinjaFileParseResult result = new NinjaFileParseResult(); |
| if (parts.isEmpty()) { |
| return result; |
| } |
| for (NinjaFileParseResult part : parts) { |
| for (Map.Entry<String, List<Pair<Long, NinjaVariableValue>>> entry : |
| part.variables.entrySet()) { |
| String name = entry.getKey(); |
| result.variables.computeIfAbsent(name, k -> Lists.newArrayList()).addAll(entry.getValue()); |
| } |
| for (Map.Entry<String, List<Pair<Long, NinjaRule>>> entry : part.rules.entrySet()) { |
| String name = entry.getKey(); |
| result.rules.computeIfAbsent(name, k -> Lists.newArrayList()).addAll(entry.getValue()); |
| } |
| for (Map.Entry<String, List<Pair<Long, NinjaPool>>> entry : part.pools.entrySet()) { |
| String name = entry.getKey(); |
| result.pools.computeIfAbsent(name, k -> Lists.newArrayList()).addAll(entry.getValue()); |
| } |
| result.targets.addAll(part.targets); |
| result.includedFilesFutures.putAll(part.includedFilesFutures); |
| result.subNinjaFilesFutures.putAll(part.subNinjaFilesFutures); |
| } |
| result.sortResults(); |
| return result; |
| } |
| |
| /** |
| * Recursively expands variables in the Ninja file and all files it includes (and subninja's). |
| * Fills in passed {@link NinjaScope} with the expanded variables and rules, and <code>rawTargets |
| * </code> - map of NinjaScope to list of fragments with unparsed Ninja targets. |
| */ |
| public void expandIntoScope(NinjaScope scope, Map<NinjaScope, List<FileFragment>> rawTargets) |
| throws InterruptedException, GenericParsingException, IOException { |
| scope.setRules(rules); |
| scope.setPools(pools); |
| rawTargets.put(scope, targets); |
| |
| TreeMap<Long, NinjaCallable> resolvables = Maps.newTreeMap(); |
| for (Map.Entry<String, List<Pair<Long, NinjaVariableValue>>> entry : variables.entrySet()) { |
| String name = entry.getKey(); |
| for (Pair<Long, NinjaVariableValue> pair : entry.getValue()) { |
| Long offset = Preconditions.checkNotNull(pair.getFirst()); |
| NinjaVariableValue variableValue = Preconditions.checkNotNull(pair.getSecond()); |
| resolvables.put( |
| offset, |
| () -> |
| scope.addExpandedVariable( |
| offset, name, scope.getExpandedValue(offset, variableValue))); |
| } |
| } |
| for (Map.Entry<Long, NinjaPromise<NinjaFileParseResult>> entry : |
| includedFilesFutures.entrySet()) { |
| Long offset = entry.getKey(); |
| resolvables.put( |
| offset, |
| () -> { |
| NinjaFileParseResult fileParseResult = entry.getValue().compute(scope); |
| NinjaScope includedScope = scope.addIncluded(offset); |
| fileParseResult.expandIntoScope(includedScope, rawTargets); |
| }); |
| } |
| for (Map.Entry<Long, NinjaPromise<NinjaFileParseResult>> entry : |
| subNinjaFilesFutures.entrySet()) { |
| Long offset = entry.getKey(); |
| resolvables.put( |
| offset, |
| () -> { |
| NinjaFileParseResult fileParseResult = entry.getValue().compute(scope); |
| NinjaScope subNinjaScope = scope.addSubNinja(entry.getKey()); |
| fileParseResult.expandIntoScope(subNinjaScope, rawTargets); |
| }); |
| } |
| |
| for (NinjaCallable ninjaCallable : resolvables.values()) { |
| ninjaCallable.call(); |
| } |
| } |
| } |