blob: c486e4b2a3b3df49cb109904f6d3175c754b841c [file] [log] [blame]
// Copyright 2017 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.analysis;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.stringtemplate.Expansion;
import com.google.devtools.build.lib.analysis.stringtemplate.ExpansionException;
import com.google.devtools.build.lib.analysis.stringtemplate.TemplateContext;
import com.google.devtools.build.lib.analysis.stringtemplate.TemplateExpander;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.packages.Type;
import com.google.devtools.build.lib.packages.Types;
import com.google.devtools.build.lib.shell.ShellUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
import javax.annotation.Nullable;
/**
* Expansion of strings and string lists by replacing make variables and $(location) functions.
*/
public final class Expander {
private final RuleContext ruleContext;
private final TemplateContext templateContext;
@Nullable ImmutableMap<Label, ImmutableCollection<Artifact>> labelMap;
/* Which variables were looked up over this instance's lifetime? */
private final TreeSet<String> lookedUpVariables;
Expander(RuleContext ruleContext, TemplateContext templateContext) {
this(ruleContext, templateContext, /* labelMap= */ null);
}
Expander(
RuleContext ruleContext,
TemplateContext templateContext,
@Nullable ImmutableMap<Label, ImmutableCollection<Artifact>> labelMap) {
this(ruleContext, templateContext, labelMap, /*lookedUpVariables=*/ null);
}
Expander(
RuleContext ruleContext,
TemplateContext templateContext,
@Nullable ImmutableMap<Label, ImmutableCollection<Artifact>> labelMap,
@Nullable TreeSet<String> lookedUpVariables) {
this.ruleContext = ruleContext;
this.templateContext = templateContext;
this.labelMap = labelMap;
// TODO(https://github.com/bazelbuild/bazel/issues/11221): Eliminate all methods that construct
// an Expander from an existing Expander. These make it hard to keep lookeduUpVariables correct.
this.lookedUpVariables = lookedUpVariables == null ? new TreeSet<>() : lookedUpVariables;
}
/**
* Returns a new instance that also expands locations using the default configuration of {@link
* LocationTemplateContext}.
*/
private Expander withLocations(boolean execPaths, boolean allowData) {
TemplateContext newTemplateContext =
new LocationTemplateContext(
templateContext, ruleContext, labelMap, execPaths, allowData, true, false);
return new Expander(ruleContext, newTemplateContext, labelMap, lookedUpVariables);
}
/**
* Returns a new instance that also expands locations, passing {@code allowData} to the underlying
* {@link LocationTemplateContext}.
*/
public Expander withDataLocations() {
return withLocations(false, true);
}
/**
* Returns a new instance that also expands locations, passing {@code allowData} and {@code
* execPaths} to the underlying {@link LocationTemplateContext}.
*/
public Expander withDataExecLocations() {
return withLocations(true, true);
}
/**
* Returns a new instance that also expands locations, passing the given location map, as well as
* {@code execPaths} to the underlying {@link LocationTemplateContext}.
*/
public Expander withExecLocationsNoSrcs(
ImmutableMap<Label, ImmutableCollection<Artifact>> locations, boolean windowsPath) {
TemplateContext newTemplateContext =
new LocationTemplateContext(
templateContext, ruleContext, locations, true, false, false, windowsPath);
return new Expander(ruleContext, newTemplateContext, labelMap, lookedUpVariables);
}
public Expander withExecLocations(ImmutableMap<Label, ImmutableCollection<Artifact>> locations) {
TemplateContext newTemplateContext =
new LocationTemplateContext(
templateContext, ruleContext, locations, true, false, true, false);
return new Expander(ruleContext, newTemplateContext, labelMap, lookedUpVariables);
}
/**
* Expands the given value string, tokenizes it, and then adds it to the given list. The attribute
* name is only used for error reporting.
*/
public void tokenizeAndExpandMakeVars(List<String> result, String attributeName, String value)
throws InterruptedException {
expandValue(result, attributeName, value, /* shouldTokenize */ true);
}
/** Expands make variables and $(location) tags in value, and optionally tokenizes the result. */
private void expandValue(
List<String> tokens, String attributeName, String value, boolean shouldTokenize)
throws InterruptedException {
value = expand(attributeName, value);
if (shouldTokenize) {
try {
ShellUtils.tokenize(tokens, value);
} catch (ShellUtils.TokenizationException e) {
ruleContext.attributeError(attributeName, e.getMessage());
}
} else {
tokens.add(value);
}
}
/**
* Returns the string "expression" after expanding all embedded references to "Make" variables. If
* any errors are encountered, they are reported, and "expression" is returned unchanged.
*
* @param attributeName the name of the attribute
* @return the expansion of "expression".
*/
public String expand(String attributeName) throws InterruptedException {
return expand(attributeName, ruleContext.attributes().get(attributeName, Type.STRING));
}
/**
* Returns the string "expression" after expanding all embedded references to "Make" variables. If
* any errors are encountered, they are reported, and "expression" is returned unchanged.
*
* @param attributeName the name of the attribute from which "expression" comes; used for error
* reporting.
* @param expression the string to expand.
* @return the expansion of "expression".
*/
public String expand(@Nullable String attributeName, String expression)
throws InterruptedException {
try {
Expansion expansion = TemplateExpander.expand(expression, templateContext);
lookedUpVariables.addAll(expansion.lookedUpVariables());
return expansion.expansion();
} catch (ExpansionException e) {
if (attributeName == null) {
ruleContext.ruleError(e.getMessage());
} else {
ruleContext.attributeError(attributeName, e.getMessage());
}
return expression;
}
}
/**
* Expands all the strings in the given list, optionally tokenizing them after expansion. The
* attribute name is only used for error reporting.
*/
private ImmutableList<String> expandAndTokenizeList(
String attrName, List<String> values, boolean shouldTokenize) throws InterruptedException {
List<String> variables = new ArrayList<>();
for (String variable : values) {
expandValue(variables, attrName, variable, shouldTokenize);
}
return ImmutableList.copyOf(variables);
}
/**
* Obtains the value of the attribute, expands all values, and returns the resulting list. If the
* attribute does not exist or is not of type {@link Types.STRING_LIST}, then this method throws
* an error.
*/
public ImmutableList<String> list(String attrName) throws InterruptedException {
return list(attrName, ruleContext.attributes().get(attrName, Types.STRING_LIST));
}
/**
* Expands all the strings in the given list. The attribute name is only used for error reporting.
*/
public ImmutableList<String> list(String attrName, List<String> values)
throws InterruptedException {
return expandAndTokenizeList(attrName, values, /* shouldTokenize */ false);
}
/**
* Obtains the value of the attribute, expands, and tokenizes all values. If the attribute does
* not exist or is not of type {@link Types.STRING_LIST}, then this method throws an error.
*/
public ImmutableList<String> tokenized(String attrName) throws InterruptedException {
return tokenized(attrName, ruleContext.attributes().get(attrName, Types.STRING_LIST));
}
/**
* Expands all the strings in the given list, and tokenizes them after expansion. The attribute
* name is only used for error reporting.
*/
public ImmutableList<String> tokenized(String attrName, List<String> values)
throws InterruptedException {
return expandAndTokenizeList(attrName, values, /* shouldTokenize */ true);
}
/**
* If the string consists of a single variable, returns the expansion of that variable. Otherwise,
* returns null. Syntax errors are reported.
*
* @param attrName the name of the attribute from which "expression" comes; used for error
* reporting.
* @param expression the string to expand.
* @return the expansion of "expression", or null.
*/
@Nullable
public String expandSingleMakeVariable(String attrName, String expression)
throws InterruptedException {
try {
return TemplateExpander.expandSingleVariable(expression, templateContext);
} catch (ExpansionException e) {
ruleContext.attributeError(attrName, e.getMessage());
return expression;
}
}
/**
* Which variables were looked up over this {@link Expander}'s lifetime?
*
* <p>The returned set is guaranteed alphabetically ordered.
*/
public ImmutableSortedSet<String> lookedUpVariables() {
return ImmutableSortedSet.copyOf(lookedUpVariables);
}
}