| // Copyright 2014 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.packages; |
| |
| import static com.google.devtools.build.lib.syntax.SkylarkType.castMap; |
| import static java.util.Collections.singleton; |
| |
| import com.google.common.base.Function; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| import com.google.common.escape.Escaper; |
| import com.google.common.escape.Escapers; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.events.Location; |
| import com.google.devtools.build.lib.syntax.ClassObject; |
| import com.google.devtools.build.lib.syntax.EvalException; |
| import com.google.devtools.build.lib.syntax.Runtime; |
| import com.google.devtools.build.lib.syntax.SkylarkCallbackFunction; |
| import com.google.devtools.build.lib.syntax.Type; |
| import com.google.devtools.build.lib.util.StringUtil; |
| import com.google.devtools.build.lib.vfs.FileSystemUtils; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import javax.annotation.Nullable; |
| |
| /** |
| * A function interface allowing rules to specify their set of implicit outputs |
| * in a more dynamic way than just simple template-substitution. For example, |
| * the set of implicit outputs may be a function of rule attributes. |
| */ |
| public abstract class ImplicitOutputsFunction { |
| |
| /** |
| * Implicit output functions for Skylark supporting key value access of expanded implicit outputs. |
| */ |
| public abstract static class SkylarkImplicitOutputsFunction extends ImplicitOutputsFunction { |
| |
| public abstract ImmutableMap<String, String> calculateOutputs(AttributeMap map) |
| throws EvalException, InterruptedException; |
| |
| @Override |
| public Iterable<String> getImplicitOutputs(AttributeMap map) |
| throws EvalException, InterruptedException { |
| return calculateOutputs(map).values(); |
| } |
| } |
| |
| /** |
| * Implicit output functions executing Skylark code. |
| */ |
| public static final class SkylarkImplicitOutputsFunctionWithCallback |
| extends SkylarkImplicitOutputsFunction { |
| |
| private final SkylarkCallbackFunction callback; |
| private final Location loc; |
| |
| public SkylarkImplicitOutputsFunctionWithCallback( |
| SkylarkCallbackFunction callback, Location loc) { |
| this.callback = callback; |
| this.loc = loc; |
| } |
| |
| @Override |
| public ImmutableMap<String, String> calculateOutputs(AttributeMap map) |
| throws EvalException, InterruptedException { |
| Map<String, Object> attrValues = new HashMap<>(); |
| for (String attrName : map.getAttributeNames()) { |
| Type<?> attrType = map.getAttributeType(attrName); |
| // Don't include configurable attributes: we don't know which value they might take |
| // since we don't yet have a build configuration. |
| if (!map.isConfigurable(attrName)) { |
| Object value = map.get(attrName, attrType); |
| attrValues.put(attrName, value == null ? Runtime.NONE : value); |
| } |
| } |
| ClassObject attrs = NativeClassObjectConstructor.STRUCT.create( |
| attrValues, |
| "Attribute '%s' either doesn't exist " |
| + "or uses a select() (i.e. could have multiple values)"); |
| try { |
| ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); |
| for (Map.Entry<String, String> entry : castMap(callback.call(attrs), |
| String.class, String.class, "implicit outputs function return value").entrySet()) { |
| |
| // Returns empty string only in case of invalid templates |
| Iterable<String> substitutions = fromTemplates(entry.getValue()).getImplicitOutputs(map); |
| if (Iterables.isEmpty(substitutions)) { |
| throw new EvalException( |
| loc, |
| String.format( |
| "For attribute '%s' in outputs: %s", |
| entry.getKey(), "Invalid placeholder(s) in template")); |
| } |
| |
| builder.put(entry.getKey(), Iterables.getOnlyElement(substitutions)); |
| } |
| return builder.build(); |
| } catch (IllegalArgumentException e) { |
| throw new EvalException(loc, e.getMessage()); |
| } |
| } |
| } |
| |
| /** |
| * Implicit output functions using a simple an output map. |
| */ |
| public static final class SkylarkImplicitOutputsFunctionWithMap |
| extends SkylarkImplicitOutputsFunction { |
| |
| private final ImmutableMap<String, String> outputMap; |
| |
| public SkylarkImplicitOutputsFunctionWithMap(ImmutableMap<String, String> outputMap) { |
| this.outputMap = outputMap; |
| } |
| |
| @Override |
| public ImmutableMap<String, String> calculateOutputs(AttributeMap map) throws EvalException { |
| |
| ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); |
| for (Map.Entry<String, String> entry : outputMap.entrySet()) { |
| // Empty iff invalid placeholders present. |
| Iterable<String> substitutions = fromTemplates(entry.getValue()).getImplicitOutputs(map); |
| if (Iterables.isEmpty(substitutions)) { |
| throw new EvalException( |
| null, |
| String.format( |
| "For attribute '%s' in outputs: %s", |
| entry.getKey(), "Invalid placeholder(s) in template")); |
| |
| } |
| |
| builder.put(entry.getKey(), Iterables.getOnlyElement(substitutions)); |
| } |
| return builder.build(); |
| } |
| } |
| |
| /** |
| * Implicit output functions which can not throw an EvalException. |
| */ |
| public abstract static class SafeImplicitOutputsFunction extends ImplicitOutputsFunction { |
| @Override |
| public abstract Iterable<String> getImplicitOutputs(AttributeMap map); |
| } |
| |
| /** |
| * An interface to objects that can retrieve rule attributes. |
| */ |
| public interface AttributeValueGetter { |
| /** |
| * Returns the value(s) of attribute "attr" in "rule", or empty set if attribute unknown. |
| */ |
| Set<String> get(AttributeMap rule, String attr); |
| } |
| |
| /** |
| * The default rule attribute retriever. |
| * |
| * <p>Custom {@link AttributeValueGetter} implementations may delegate to this object as a |
| * fallback mechanism. |
| */ |
| public static final AttributeValueGetter DEFAULT_RULE_ATTRIBUTE_GETTER = |
| new AttributeValueGetter() { |
| @Override |
| public Set<String> get(AttributeMap rule, String attr) { |
| return attributeValues(rule, attr); |
| } |
| }; |
| |
| private static final Escaper PERCENT_ESCAPER = Escapers.builder().addEscape('%', "%%").build(); |
| |
| /** |
| * Given a newly-constructed Rule instance (with attributes populated), |
| * returns the list of output files that this rule produces implicitly. |
| */ |
| public abstract Iterable<String> getImplicitOutputs(AttributeMap rule) |
| throws EvalException, InterruptedException; |
| |
| /** |
| * The implicit output function that returns no files. |
| */ |
| public static final SafeImplicitOutputsFunction NONE = new SafeImplicitOutputsFunction() { |
| @Override public Iterable<String> getImplicitOutputs(AttributeMap rule) { |
| return Collections.emptyList(); |
| } |
| }; |
| |
| /** |
| * A convenience wrapper for {@link #fromTemplates(Iterable)}. |
| */ |
| public static SafeImplicitOutputsFunction fromTemplates(String... templates) { |
| return fromTemplates(Arrays.asList(templates)); |
| } |
| |
| /** |
| * The implicit output function that generates files based on a set of |
| * template substitutions using rule attribute values. |
| * |
| * @param templates The templates used to construct the name of the implicit |
| * output file target. The substring "%{name}" will be replaced by the |
| * actual name of the rule, the substring "%{srcs}" will be replaced by the |
| * name of each source file without its extension. If multiple %{} |
| * substrings exist, the cross-product of them is generated. |
| */ |
| public static SafeImplicitOutputsFunction fromTemplates(final Iterable<String> templates) { |
| return new SafeImplicitOutputsFunction() { |
| // TODO(bazel-team): parse the templates already here |
| @Override |
| public Iterable<String> getImplicitOutputs(AttributeMap rule) { |
| Iterable<String> result = null; |
| for (String template : templates) { |
| List<String> substitutions = substitutePlaceholderIntoTemplate(template, rule); |
| if (substitutions.isEmpty()) { |
| continue; |
| } |
| if (result == null) { |
| result = substitutions; |
| } else { |
| result = Iterables.concat(result, substitutions); |
| } |
| } |
| if (result == null) { |
| return ImmutableList.<String>of(); |
| } else { |
| return Sets.newLinkedHashSet(result); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return StringUtil.joinEnglishList(templates); |
| } |
| }; |
| } |
| |
| /** |
| * A convenience wrapper for {@link #fromFunctions(Iterable)}. |
| */ |
| public static SafeImplicitOutputsFunction fromFunctions( |
| SafeImplicitOutputsFunction... functions) { |
| return fromFunctions(Arrays.asList(functions)); |
| } |
| |
| /** |
| * The implicit output function that generates files based on a set of |
| * template substitutions using rule attribute values. |
| * |
| * @param functions The functions used to construct the name of the implicit |
| * output file target. The substring "%{name}" will be replaced by the |
| * actual name of the rule, the substring "%{srcs}" will be replaced by the |
| * name of each source file without its extension. If multiple %{} |
| * substrings exist, the cross-product of them is generated. |
| */ |
| public static SafeImplicitOutputsFunction fromFunctions( |
| final Iterable<SafeImplicitOutputsFunction> functions) { |
| return new SafeImplicitOutputsFunction() { |
| @Override |
| public Iterable<String> getImplicitOutputs(AttributeMap rule) { |
| Collection<String> result = new LinkedHashSet<>(); |
| for (SafeImplicitOutputsFunction function : functions) { |
| Iterables.addAll(result, function.getImplicitOutputs(rule)); |
| } |
| return result; |
| } |
| @Override |
| public String toString() { |
| return StringUtil.joinEnglishList(functions); |
| } |
| }; |
| } |
| |
| /** |
| * Coerces attribute "attrName" of the specified rule into a sequence of |
| * strings. Helper function for {@link #fromTemplates(Iterable)}. |
| */ |
| private static Set<String> attributeValues(AttributeMap rule, String attrName) { |
| if (attrName.equals("dirname")) { |
| PathFragment dir = PathFragment.create(rule.getName()).getParentDirectory(); |
| return (dir.segmentCount() == 0) ? singleton("") : singleton(dir.getPathString() + "/"); |
| } else if (attrName.equals("basename")) { |
| return singleton(PathFragment.create(rule.getName()).getBaseName()); |
| } |
| |
| Type<?> attrType = rule.getAttributeType(attrName); |
| if (attrType == null) { |
| return Collections.emptySet(); |
| } |
| // String attributes and lists are easy. |
| if (Type.STRING == attrType) { |
| return singleton(rule.get(attrName, Type.STRING)); |
| } else if (Type.STRING_LIST == attrType) { |
| return Sets.newLinkedHashSet(rule.get(attrName, Type.STRING_LIST)); |
| } else if (BuildType.LABEL == attrType) { |
| // Labels are most often used to change the extension, |
| // e.g. %.foo -> %.java, so we return the basename w/o extension. |
| Label label = rule.get(attrName, BuildType.LABEL); |
| return singleton(FileSystemUtils.removeExtension(label.getName())); |
| } else if (BuildType.LABEL_LIST == attrType) { |
| // Labels are most often used to change the extension, |
| // e.g. %.foo -> %.java, so we return the basename w/o extension. |
| return Sets.newLinkedHashSet( |
| Iterables.transform(rule.get(attrName, BuildType.LABEL_LIST), |
| new Function<Label, String>() { |
| @Override |
| public String apply(Label label) { |
| return FileSystemUtils.removeExtension(label.getName()); |
| } |
| })); |
| } else if (BuildType.OUTPUT == attrType) { |
| Label out = rule.get(attrName, BuildType.OUTPUT); |
| return singleton(out.getName()); |
| } else if (BuildType.OUTPUT_LIST == attrType) { |
| return Sets.newLinkedHashSet( |
| Iterables.transform(rule.get(attrName, BuildType.OUTPUT_LIST), |
| new Function<Label, String>() { |
| @Override |
| public String apply(Label label) { |
| return label.getName(); |
| } |
| })); |
| } |
| throw new IllegalArgumentException( |
| "Don't know how to handle " + attrName + " : " + attrType); |
| } |
| |
| /** |
| * Collects all named placeholders from the template while replacing them with %s. |
| * |
| * <p>Example: for {@code template} "%{name}_%{locales}.foo", it will return "%s_%s.foo" and |
| * store "name" and "locales" in {@code placeholders}. |
| * |
| * <p>Incomplete placeholders are treated like text: for "a-%{x}-%{y" this method returns |
| * "a-%s-%%{y" and stores "x" in {@code placeholders}. |
| * |
| * @param template a string with placeholders of the format %{...} |
| * @param placeholders a collection to collect placeholders into; may contain duplicates if not a |
| * Set |
| * @return a format string for {@link String#format}, created from the template string with every |
| * placeholder replaced by %s |
| */ |
| public static String createPlaceholderSubstitutionFormatString(String template, |
| Collection<String> placeholders) { |
| return createPlaceholderSubstitutionFormatStringRecursive(template, placeholders, |
| new StringBuilder()); |
| } |
| |
| private static String createPlaceholderSubstitutionFormatStringRecursive(String template, |
| Collection<String> placeholders, StringBuilder formatBuilder) { |
| int start = template.indexOf("%{"); |
| if (start < 0) { |
| return formatBuilder.append(PERCENT_ESCAPER.escape(template)).toString(); |
| } |
| |
| int end = template.indexOf('}', start + 2); |
| if (end < 0) { |
| return formatBuilder.append(PERCENT_ESCAPER.escape(template)).toString(); |
| } |
| |
| formatBuilder.append(PERCENT_ESCAPER.escape(template.substring(0, start))).append("%s"); |
| placeholders.add(template.substring(start + 2, end)); |
| return createPlaceholderSubstitutionFormatStringRecursive(template.substring(end + 1), |
| placeholders, formatBuilder); |
| } |
| |
| /** |
| * Given a template string, replaces all placeholders of the form %{...} with |
| * the values from attributeSource. If there are multiple placeholders, then |
| * the output is the cross product of substitutions. |
| */ |
| public static ImmutableList<String> substitutePlaceholderIntoTemplate(String template, |
| AttributeMap rule) { |
| return substitutePlaceholderIntoTemplate(template, rule, DEFAULT_RULE_ATTRIBUTE_GETTER, null); |
| } |
| |
| /** |
| * Substitutes attribute-placeholders in a template string, producing all possible combinations. |
| * |
| * @param template the template string, may contain named placeholders for rule attributes, like |
| * <code>%{name}</code> or <code>%{deps}</code> |
| * @param rule the rule whose attributes the placeholders correspond to |
| * @param placeholdersInTemplate if specified, will contain all placeholders found in the |
| * template; may contain duplicates |
| * @return all possible combinations of the attributes referenced by the placeholders, |
| * substituted into the template; empty if any of the placeholders expands to no values |
| */ |
| public static ImmutableList<String> substitutePlaceholderIntoTemplate(String template, |
| AttributeMap rule, AttributeValueGetter attributeGetter, |
| @Nullable List<String> placeholdersInTemplate) { |
| List<String> placeholders = (placeholdersInTemplate == null) |
| ? Lists.<String>newArrayList() |
| : placeholdersInTemplate; |
| String formatStr = createPlaceholderSubstitutionFormatString(template, placeholders); |
| if (placeholders.isEmpty()) { |
| return ImmutableList.of(template); |
| } |
| |
| List<Set<String>> values = Lists.newArrayListWithCapacity(placeholders.size()); |
| for (String placeholder : placeholders) { |
| Set<String> attrValues = attributeGetter.get(rule, placeholder); |
| if (attrValues.isEmpty()) { |
| return ImmutableList.<String>of(); |
| } |
| values.add(attrValues); |
| } |
| ImmutableList.Builder<String> out = new ImmutableList.Builder<>(); |
| for (List<String> combination : Sets.cartesianProduct(values)) { |
| out.add(String.format(formatStr, combination.toArray())); |
| } |
| return out.build(); |
| } |
| } |