| // 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.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, 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 withExecLocations( | |
| ImmutableMap<Label, ImmutableCollection<Artifact>> locations, boolean windowsPath) { | |
| TemplateContext newTemplateContext = | |
| new LocationTemplateContext( | |
| templateContext, ruleContext, locations, true, false, windowsPath); | |
| return new Expander(ruleContext, newTemplateContext, labelMap, lookedUpVariables); | |
| } | |
| public Expander withExecLocations(ImmutableMap<Label, ImmutableCollection<Artifact>> locations) { | |
| return withExecLocations(locations, false); | |
| } | |
| /** | |
| * 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) { | |
| 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) { | |
| 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) { | |
| 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) { | |
| 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) { | |
| 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 Type#STRING_LIST}, then this method throws | |
| * an error. | |
| */ | |
| public ImmutableList<String> list(String attrName) { | |
| return list(attrName, ruleContext.attributes().get(attrName, Type.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) { | |
| 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 Type#STRING_LIST}, then this method throws an error. | |
| */ | |
| public ImmutableList<String> tokenized(String attrName) { | |
| return tokenized(attrName, ruleContext.attributes().get(attrName, Type.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) { | |
| 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) { | |
| 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); | |
| } | |
| } |