blob: cd4a5420ef474f146735f0cb7d39a4c63a05b2ab [file] [log] [blame]
// 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();
}
}
}