diff options
author | 2015-12-16 15:10:20 +0000 | |
---|---|---|
committer | 2015-12-16 15:31:44 +0000 | |
commit | ace678e16def33a94ad1cb3bec7336d894510272 (patch) | |
tree | f83705c727e7af498cc2e2ad0e75020d866d14c0 /src/main/java/com/google/devtools/build/lib | |
parent | 8853df9a19dabc72c3b723b84e6cb69b6fb2884e (diff) |
Implement aspect attributes and expose them to aspect implementation function.
--
MOS_MIGRATED_REVID=110356954
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib')
7 files changed, 384 insertions, 219 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java index b75c6b9d11..cc9951458f 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java @@ -136,7 +136,7 @@ public final class RuleContext extends TargetContext private final Set<ConfigMatchingProvider> configConditions; private final AttributeMap attributes; private final ImmutableSet<String> features; - private final Map<String, Attribute> aspectAttributes; + private final ImmutableMap<String, Attribute> aspectAttributes; private final BuildConfiguration hostConfiguration; private final ConfigurationFragmentPolicy configurationFragmentPolicy; private final Class<? extends BuildConfiguration.Fragment> universalFragment; @@ -147,11 +147,13 @@ public final class RuleContext extends TargetContext /* lazily computed cache for Make variables, computed from the above. See get... method */ private transient ConfigurationMakeVariableContext configurationMakeVariableContext = null; - private RuleContext(Builder builder, ListMultimap<String, ConfiguredTarget> targetMap, + private RuleContext( + Builder builder, + ListMultimap<String, ConfiguredTarget> targetMap, ListMultimap<String, ConfiguredFilesetEntry> filesetEntryMap, Set<ConfigMatchingProvider> configConditions, Class<? extends BuildConfiguration.Fragment> universalFragment, - Map<String, Attribute> aspectAttributes) { + ImmutableMap<String, Attribute> aspectAttributes) { super(builder.env, builder.rule, builder.configuration, builder.prerequisiteMap.get(null), builder.visibility); this.rule = builder.rule; @@ -234,6 +236,13 @@ public final class RuleContext extends TargetContext } /** + * Attributes from aspects. + */ + public ImmutableMap<String, Attribute> getAspectAttributes() { + return aspectAttributes; + } + + /** * Accessor for the Rule's attribute values. */ public AttributeMap attributes() { @@ -1241,7 +1250,7 @@ public final class RuleContext extends TargetContext private ListMultimap<Attribute, ConfiguredTarget> prerequisiteMap; private Set<ConfigMatchingProvider> configConditions; private NestedSet<PackageSpecification> visibility; - private Map<String, Attribute> aspectAttributes; + private ImmutableMap<String, Attribute> aspectAttributes; Builder(AnalysisEnvironment env, Rule rule, BuildConfiguration configuration, BuildConfiguration hostConfiguration, @@ -1284,7 +1293,7 @@ public final class RuleContext extends TargetContext * Adds attributes which are defined by an Aspect (and not by RuleClass). */ Builder setAspectAttributes(Map<String, Attribute> aspectAttributes) { - this.aspectAttributes = aspectAttributes; + this.aspectAttributes = ImmutableMap.copyOf(aspectAttributes); return this; } diff --git a/src/main/java/com/google/devtools/build/lib/packages/AspectDefinition.java b/src/main/java/com/google/devtools/build/lib/packages/AspectDefinition.java index 15829d8216..763ae117c0 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/AspectDefinition.java +++ b/src/main/java/com/google/devtools/build/lib/packages/AspectDefinition.java @@ -250,6 +250,17 @@ public final class AspectDefinition { */ public <TYPE> Builder add(Attribute.Builder<TYPE> attr) { Attribute attribute = attr.build(); + return add(attribute); + } + + /** + * Adds an attribute to the aspect. + * + * <p>Since aspects do not appear in BUILD files, the attribute must be either implicit + * (not available in the BUILD file, starting with '$') or late-bound (determined after the + * configuration is available, starting with ':') + */ + public Builder add(Attribute attribute) { Preconditions.checkState(attribute.isImplicit() || attribute.isLateBound()); Preconditions.checkState(!attributes.containsKey(attribute.getName()), "An attribute with the name '%s' already exists.", attribute.getName()); diff --git a/src/main/java/com/google/devtools/build/lib/packages/Attribute.java b/src/main/java/com/google/devtools/build/lib/packages/Attribute.java index 8cd855ce49..3bbccdfc26 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/Attribute.java +++ b/src/main/java/com/google/devtools/build/lib/packages/Attribute.java @@ -479,6 +479,10 @@ public final class Attribute implements Comparable<Attribute> { return this; } + public boolean isValueSet() { + return valueSet; + } + /** * Sets the attribute default value to a computed default value - use * this when the default value is a function of other attributes of the diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java index c3c48972e4..ac705a5f2b 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java +++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java @@ -62,6 +62,7 @@ import com.google.devtools.build.lib.packages.RuleFactory.InvalidRuleException; import com.google.devtools.build.lib.packages.SkylarkAspectClass; import com.google.devtools.build.lib.packages.TargetUtils; import com.google.devtools.build.lib.packages.TestSize; +import com.google.devtools.build.lib.rules.SkylarkAttr.Descriptor; import com.google.devtools.build.lib.syntax.BaseFunction; import com.google.devtools.build.lib.syntax.BuiltinFunction; import com.google.devtools.build.lib.syntax.ClassObject; @@ -202,7 +203,6 @@ public class SkylarkRuleClassFunctions { } // TODO(bazel-team): implement attribute copy and other rule properties - @SkylarkSignature(name = "rule", doc = "Creates a new rule. Store it in a global value, so that it can be loaded and called " + "from BUILD files.", @@ -243,14 +243,14 @@ public class SkylarkRuleClassFunctions { doc = "If true, the files will be generated in the genfiles directory instead of the " + "bin directory. Unless you need it for compatibility with existing rules " + "(e.g. when generating header files for C++), do not set this flag."), - @Param(name = "fragments", type = SkylarkList.class, generic1 = String.class, - defaultValue = "[]", - doc = "List of names of configuration fragments that the rule requires " - + "in target configuration."), - @Param(name = "host_fragments", type = SkylarkList.class, generic1 = String.class, - defaultValue = "[]", - doc = "List of names of configuration fragments that the rule requires " - + "in host configuration.")}, + @Param(name = "fragments", type = SkylarkList.class, generic1 = String.class, + defaultValue = "[]", + doc = "List of names of configuration fragments that the rule requires " + + "in target configuration."), + @Param(name = "host_fragments", type = SkylarkList.class, generic1 = String.class, + defaultValue = "[]", + doc = "List of names of configuration fragments that the rule requires " + + "in host configuration.")}, useAst = true, useEnvironment = true) private static final BuiltinFunction rule = new BuiltinFunction("rule") { @SuppressWarnings({"rawtypes", "unchecked"}) // castMap produces @@ -265,19 +265,8 @@ public class SkylarkRuleClassFunctions { // We'll set the name later, pass the empty string for now. RuleClass.Builder builder = new RuleClass.Builder("", type, true, parent); - ImmutableList.Builder<Pair<String, SkylarkAttr.Descriptor>> attributes = - ImmutableList.builder(); - - if (attrs != Runtime.NONE) { - for (Map.Entry<String, SkylarkAttr.Descriptor> attr : - castMap(attrs, String.class, SkylarkAttr.Descriptor.class, "attrs").entrySet()) { - SkylarkAttr.Descriptor attrDescriptor = attr.getValue(); - String attrName = - attributeToNative(attr.getKey(), ast.getLocation(), - attrDescriptor.getAttributeBuilder().hasLateBoundValue()); - attributes.add(Pair.of(attrName, attrDescriptor)); - } - } + ImmutableList<Pair<String, SkylarkAttr.Descriptor>> attributes = + attrObjectToAttributesList(attrs, ast); if (executable || test) { addAttribute( ast.getLocation(), @@ -292,16 +281,18 @@ public class SkylarkRuleClassFunctions { if (implicitOutputs != Runtime.NONE) { if (implicitOutputs instanceof BaseFunction) { BaseFunction func = (BaseFunction) implicitOutputs; - SkylarkCallbackFunction callback = - new SkylarkCallbackFunction(func, ast, funcallEnv); + SkylarkCallbackFunction callback = new SkylarkCallbackFunction(func, ast, funcallEnv); builder.setImplicitOutputsFunction( new SkylarkImplicitOutputsFunctionWithCallback(callback, ast.getLocation())); } else { builder.setImplicitOutputsFunction( new SkylarkImplicitOutputsFunctionWithMap( - ImmutableMap.copyOf(castMap( - implicitOutputs, String.class, String.class, - "implicit outputs of the rule class")))); + ImmutableMap.copyOf( + castMap( + implicitOutputs, + String.class, + String.class, + "implicit outputs of the rule class")))); } } @@ -312,27 +303,48 @@ public class SkylarkRuleClassFunctions { registerRequiredFragments(fragments, hostFragments, builder); builder.setConfiguredTargetFunction(implementation); builder.setRuleDefinitionEnvironment(funcallEnv); - return new RuleFunction(builder, type, attributes.build(), ast.getLocation()); + return new RuleFunction(builder, type, attributes, ast.getLocation()); } + private void registerRequiredFragments( + SkylarkList fragments, SkylarkList hostFragments, RuleClass.Builder builder) + throws EvalException { + Map<ConfigurationTransition, ImmutableSet<String>> map = new HashMap<>(); + addFragmentsToMap(map, fragments, NONE); // NONE represents target configuration + addFragmentsToMap(map, hostFragments, HOST); - private void registerRequiredFragments( - SkylarkList fragments, SkylarkList hostFragments, RuleClass.Builder builder) - throws EvalException { - Map<ConfigurationTransition, ImmutableSet<String>> map = new HashMap<>(); - addFragmentsToMap(map, fragments, NONE); // NONE represents target configuration - addFragmentsToMap(map, hostFragments, HOST); + builder.requiresConfigurationFragments(new SkylarkModuleNameResolver(), map); + } - builder.requiresConfigurationFragments(new SkylarkModuleNameResolver(), map); - } + private void addFragmentsToMap( + Map<ConfigurationTransition, ImmutableSet<String>> map, + SkylarkList fragments, + ConfigurationTransition config) + throws EvalException { + if (!fragments.isEmpty()) { + map.put(config, ImmutableSet.copyOf(fragments.getContents(String.class, "fragments"))); + } + } + }; - private void addFragmentsToMap(Map<ConfigurationTransition, ImmutableSet<String>> map, - SkylarkList fragments, ConfigurationTransition config) throws EvalException { - if (!fragments.isEmpty()) { - map.put(config, ImmutableSet.copyOf(fragments.getContents(String.class, "fragments"))); + protected static ImmutableList<Pair<String, Descriptor>> attrObjectToAttributesList( + Object attrs, FuncallExpression ast) throws EvalException { + ImmutableList.Builder<Pair<String, Descriptor>> attributes = ImmutableList.builder(); + + if (attrs != Runtime.NONE) { + for (Map.Entry<String, Descriptor> attr : + castMap(attrs, String.class, Descriptor.class, "attrs").entrySet()) { + Descriptor attrDescriptor = attr.getValue(); + String attrName = + attributeToNative( + attr.getKey(), + ast.getLocation(), + attrDescriptor.getAttributeBuilder().hasLateBoundValue()); + attributes.add(Pair.of(attrName, attrDescriptor)); } } - }; + return attributes.build(); + } private static void addAttribute( Location location, RuleClass.Builder builder, Attribute attribute) throws EvalException { @@ -356,12 +368,7 @@ public class SkylarkRuleClassFunctions { generic1 = String.class, defaultValue = "[]" ), - @Param( - name = "extra_deps", - type = SkylarkList.class, - generic1 = String.class, - defaultValue = "[]" - ) + @Param(name = "attrs", type = Map.class, noneable = true, defaultValue = "None") }, useEnvironment = true, useAst = true @@ -371,29 +378,37 @@ public class SkylarkRuleClassFunctions { public SkylarkAspect invoke( BaseFunction implementation, SkylarkList attributeAspects, - SkylarkList extraDeps, + Object attrs, FuncallExpression ast, - Environment funcallEnv) throws EvalException { - ImmutableList.Builder<String> attributeListBuilder = ImmutableList.builder(); + Environment funcallEnv) + throws EvalException { + ImmutableList.Builder<String> attrAspects = ImmutableList.builder(); for (Object attributeAspect : attributeAspects) { - attributeListBuilder.add(STRING.convert(attributeAspect, "attr_aspects")); + attrAspects.add(STRING.convert(attributeAspect, "attr_aspects")); } - ImmutableList.Builder<Label> extraDepsBuilder = ImmutableList.builder(); - for (Object extraDep : extraDeps) { - String extraDepsString = STRING.convert(extraDep, "extra_deps"); - Label label; - try { - label = Label.parseAbsolute(extraDepsString); - } catch (LabelSyntaxException e) { - throw new EvalException(ast.getLocation(), e.getMessage()); + + ImmutableList<Pair<String, SkylarkAttr.Descriptor>> attributes = + attrObjectToAttributesList(attrs, ast); + for (Pair<String, Descriptor> attribute : attributes) { + String nativeName = attribute.getFirst(); + if (!Attribute.isImplicit(nativeName)) { + throw new EvalException( + ast.getLocation(), + String.format( + "Aspect attribute '%s' must be implicit (its name should start with '_').", + nativeName)); + } + + String skylarkName = "_" + nativeName.substring(1); + + if (!attribute.getSecond().getAttributeBuilder().isValueSet()) { + throw new EvalException( + ast.getLocation(), + String.format("Aspect attribute '%s' has no default value.", skylarkName)); } - extraDepsBuilder.add(label); } - return new SkylarkAspect(implementation, - attributeListBuilder.build(), - extraDepsBuilder.build(), - funcallEnv); + return new SkylarkAspect(implementation, attrAspects.build(), attributes, funcallEnv); } }; @@ -634,17 +649,18 @@ public class SkylarkRuleClassFunctions { public static final class SkylarkAspect implements SkylarkValue { private final BaseFunction implementation; private final ImmutableList<String> attributeAspects; - private final ImmutableList<Label> extraDeps; + private final ImmutableList<Pair<String, Descriptor>> attributes; private final Environment funcallEnv; private Exported exported; public SkylarkAspect( BaseFunction implementation, ImmutableList<String> attributeAspects, - ImmutableList<Label> extraDeps, Environment funcallEnv) { + ImmutableList<Pair<String, Descriptor>> attributes, + Environment funcallEnv) { this.implementation = implementation; this.attributeAspects = attributeAspects; - this.extraDeps = extraDeps; + this.attributes = attributes; this.funcallEnv = funcallEnv; } @@ -660,8 +676,8 @@ public class SkylarkRuleClassFunctions { return funcallEnv; } - public ImmutableList<Label> getExtraDeps() { - return extraDeps; + public ImmutableList<Pair<String, Descriptor>> getAttributes() { + return attributes; } @Override @@ -737,9 +753,11 @@ public class SkylarkRuleClassFunctions { for (String attributeAspect : skylarkAspect.getAttributeAspects()) { builder.attributeAspect(attributeAspect, this); } - builder.add(attr("$extra_deps", LABEL_LIST).value(skylarkAspect.getExtraDeps())); + ImmutableList<Pair<String, Descriptor>> attributes = skylarkAspect.getAttributes(); + for (Pair<String, Descriptor> attribute : attributes) { + builder.add(attribute.second.getAttributeBuilder().build(attribute.first)); + } this.aspectDefinition = builder.build(); - } @Override diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java index 31693fc200..ad7ff5e088 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java +++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java @@ -27,6 +27,7 @@ import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.TargetUtils; +import com.google.devtools.build.lib.rules.SkylarkRuleContext.Kind; import com.google.devtools.build.lib.syntax.BaseFunction; import com.google.devtools.build.lib.syntax.ClassObject; import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject; @@ -53,7 +54,7 @@ public final class SkylarkRuleConfiguredTargetBuilder { throws InterruptedException { String expectFailure = ruleContext.attributes().get("expect_failure", Type.STRING); try (Mutability mutability = Mutability.create("configured target")) { - SkylarkRuleContext skylarkRuleContext = new SkylarkRuleContext(ruleContext); + SkylarkRuleContext skylarkRuleContext = new SkylarkRuleContext(ruleContext, Kind.RULE); Environment env = Environment.builder(mutability) .setSkylark() .setGlobals( diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java index 54c9b7ff65..d335cc4f53 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java +++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java @@ -13,11 +13,13 @@ // limitations under the License. package com.google.devtools.build.lib.rules; +import com.google.common.base.Function; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.actions.Artifact; @@ -34,6 +36,7 @@ import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.FragmentCollection; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.packages.Attribute; import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition; import com.google.devtools.build.lib.packages.BuildType; @@ -57,6 +60,7 @@ import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -94,6 +98,53 @@ public final class SkylarkRuleContext { return Class.forName(classPath); } }); + public static final String EXECUTABLE_DOC = + "A <code>struct</code> containing executable files defined in label type " + + "attributes marked as <code>executable=True</code>. The struct fields correspond " + + "to the attribute names. Each struct value is always a <code>file</code>s or " + + "<code>None</code>. If an optional attribute is not specified in the rule " + + "then the corresponding struct value is <code>None</code>. If a label type is not " + + "marked as <code>executable=True</code>, no corresponding struct field is generated."; + public static final String FILES_DOC = + "A <code>struct</code> containing files defined in label or label list " + + "type attributes. The struct fields correspond to the attribute names. The struct " + + "values are <code>list</code> of <code>file</code>s. If an optional attribute is " + + "not specified in the rule, an empty list is generated." + + "It is a shortcut for:" + + "<pre class=language-python>[f for t in ctx.attr.<ATTR> for f in t.files]</pre>"; + public static final String FILE_DOC = + "A <code>struct</code> containing files defined in label type " + + "attributes marked as <code>single_file=True</code>. The struct fields correspond " + + "to the attribute names. The struct value is always a <code>file</code> or " + + "<code>None</code>. If an optional attribute is not specified in the rule " + + "then the corresponding struct value is <code>None</code>. If a label type is not " + + "marked as <code>single_file=True</code>, no corresponding struct field is generated. " + + "It is a shortcut for:" + + "<pre class=language-python>list(ctx.attr.<ATTR>.files)[0]</pre>"; + public static final String ATTR_DOC = + "A struct to access the values of the attributes. The values are provided by " + + "the user (if not, a default value is used)."; + public static final String OUTPUTS_DOC = + "A <code>struct</code> containing all the output files." + + " The struct is generated the following way:<br>" + + "<ul><li>If the rule is marked as <code>executable=True</code> the struct has an " + + "\"executable\" field with the rules default executable <code>file</code> value." + + "<li>For every entry in the rule's <code>outputs</code> dict an attr is generated with " + + "the same name and the corresponding <code>file</code> value." + + "<li>For every output type attribute a struct attribute is generated with the " + + "same name and the corresponding <code>file</code> value or <code>None</code>, " + + "if no value is specified in the rule." + + "<li>For every output list type attribute a struct attribute is generated with the " + + "same name and corresponding <code>list</code> of <code>file</code>s value " + + "(an empty list if no value is specified in the rule).</ul>"; + public static final Function<Attribute, Object> ATTRIBUTE_VALUE_EXTRACTOR_FOR_ASPECT = + new Function<Attribute, Object>() { + @Nullable + @Override + public Object apply(Attribute attribute) { + return attribute.getDefaultValue(null); + } + }; private final RuleContext ruleContext; @@ -101,94 +152,134 @@ public final class SkylarkRuleContext { private final FragmentCollection hostFragments; - // TODO(bazel-team): support configurable attributes. - private final SkylarkClassObject attrObject; - - private final SkylarkClassObject outputsObject; - - private final SkylarkClassObject executableObject; - - private final SkylarkClassObject fileObject; - - private final SkylarkClassObject filesObject; + private final ImmutableMap<String, String> makeVariables; + private final SkylarkRuleAttributesCollection attributesCollection; + private final SkylarkRuleAttributesCollection ruleAttributesCollection; // TODO(bazel-team): we only need this because of the css_binary rule. - private final ImmutableMap<Artifact, Label> artifactLabelMap; - - private final ImmutableMap<Artifact, FilesToRunProvider> executableRunfilesMap; + private final ImmutableMap<Artifact, Label> artifactsLabelMap; + private final SkylarkClassObject outputsObject; - private final ImmutableMap<String, String> makeVariables; + /** + * Determines whether this context is for rule implementation or for aspect implementation. + */ + public enum Kind { + RULE, + ASPECT + } /** * Creates a new SkylarkRuleContext using ruleContext. - * @throws InterruptedException + * @throws InterruptedException */ - public SkylarkRuleContext(RuleContext ruleContext) throws EvalException, InterruptedException { + public SkylarkRuleContext(RuleContext ruleContext, Kind kind) + throws EvalException, InterruptedException { this.ruleContext = Preconditions.checkNotNull(ruleContext); fragments = new FragmentCollection(ruleContext, ConfigurationTransition.NONE); hostFragments = new FragmentCollection(ruleContext, ConfigurationTransition.HOST); - HashMap<String, Object> outputsBuilder = new HashMap<>(); - if (ruleContext.getRule().getRuleClassObject().outputsDefaultExecutable()) { - addOutput(outputsBuilder, "executable", ruleContext.createOutputArtifact()); - } - ImplicitOutputsFunction implicitOutputsFunction = - ruleContext.getRule().getRuleClassObject().getImplicitOutputsFunction(); - - if (implicitOutputsFunction instanceof SkylarkImplicitOutputsFunction) { - SkylarkImplicitOutputsFunction func = (SkylarkImplicitOutputsFunction) - ruleContext.getRule().getRuleClassObject().getImplicitOutputsFunction(); - for (Map.Entry<String, String> entry : func.calculateOutputs( - RawAttributeMapper.of(ruleContext.getRule())).entrySet()) { - addOutput(outputsBuilder, entry.getKey(), - ruleContext.getImplicitOutputArtifact(entry.getValue())); + if (kind == Kind.RULE) { + Collection<Attribute> attributes = ruleContext.getRule().getAttributes(); + HashMap<String, Object> outputsBuilder = new HashMap<>(); + if (ruleContext.getRule().getRuleClassObject().outputsDefaultExecutable()) { + addOutput(outputsBuilder, "executable", ruleContext.createOutputArtifact()); } - } + ImplicitOutputsFunction implicitOutputsFunction = + ruleContext.getRule().getRuleClassObject().getImplicitOutputsFunction(); - ImmutableMap.Builder<Artifact, Label> artifactLabelMapBuilder = - ImmutableMap.builder(); - for (Attribute a : ruleContext.getRule().getAttributes()) { - String attrName = a.getName(); - Type<?> type = a.getType(); - if (type != BuildType.OUTPUT && type != BuildType.OUTPUT_LIST) { - continue; - } - ImmutableList.Builder<Artifact> artifactsBuilder = ImmutableList.builder(); - for (OutputFile outputFile : ruleContext.getRule().getOutputFileMap().get(attrName)) { - Artifact artifact = ruleContext.createOutputArtifact(outputFile); - artifactsBuilder.add(artifact); - artifactLabelMapBuilder.put(artifact, outputFile.getLabel()); + if (implicitOutputsFunction instanceof SkylarkImplicitOutputsFunction) { + SkylarkImplicitOutputsFunction func = + (SkylarkImplicitOutputsFunction) + ruleContext.getRule().getRuleClassObject().getImplicitOutputsFunction(); + for (Map.Entry<String, String> entry : + func.calculateOutputs(RawAttributeMapper.of(ruleContext.getRule())).entrySet()) { + addOutput( + outputsBuilder, + entry.getKey(), + ruleContext.getImplicitOutputArtifact(entry.getValue())); + } } - ImmutableList<Artifact> artifacts = artifactsBuilder.build(); - if (type == BuildType.OUTPUT) { - if (artifacts.size() == 1) { - addOutput(outputsBuilder, attrName, Iterables.getOnlyElement(artifacts)); + Builder<Artifact, Label> artifactLabelMapBuilder = ImmutableMap.builder(); + for (Attribute a : attributes) { + String attrName = a.getName(); + Type<?> type = a.getType(); + if (type != BuildType.OUTPUT && type != BuildType.OUTPUT_LIST) { + continue; + } + ImmutableList.Builder<Artifact> artifactsBuilder = ImmutableList.builder(); + for (OutputFile outputFile : ruleContext.getRule().getOutputFileMap().get(attrName)) { + Artifact artifact = ruleContext.createOutputArtifact(outputFile); + artifactsBuilder.add(artifact); + artifactLabelMapBuilder.put(artifact, outputFile.getLabel()); + } + ImmutableList<Artifact> artifacts = artifactsBuilder.build(); + + if (type == BuildType.OUTPUT) { + if (artifacts.size() == 1) { + addOutput(outputsBuilder, attrName, Iterables.getOnlyElement(artifacts)); + } else { + addOutput(outputsBuilder, attrName, Runtime.NONE); + } + } else if (type == BuildType.OUTPUT_LIST) { + addOutput(outputsBuilder, attrName, new MutableList(artifacts)); } else { - addOutput(outputsBuilder, attrName, Runtime.NONE); + throw new IllegalArgumentException( + "Type of " + attrName + "(" + type + ") is not output type "); } - } else if (type == BuildType.OUTPUT_LIST) { - addOutput(outputsBuilder, attrName, new MutableList(artifacts)); - } else { - throw new IllegalArgumentException( - "Type of " + attrName + "(" + type + ") is not output type "); } + + this.artifactsLabelMap = artifactLabelMapBuilder.build(); + this.outputsObject = + new SkylarkClassObject( + outputsBuilder, + "No attribute '%s' in outputs. Make sure you declared a rule output with this name."); + + this.attributesCollection = + buildAttributesCollection( + attributes, ruleContext, attributeValueExtractorForRule(ruleContext)); + this.ruleAttributesCollection = null; + } else { // ASPECT + this.artifactsLabelMap = ImmutableMap.of(); + this.outputsObject = null; + this.attributesCollection = + buildAttributesCollection( + ruleContext.getAspectAttributes().values(), + ruleContext, + ATTRIBUTE_VALUE_EXTRACTOR_FOR_ASPECT); + this.ruleAttributesCollection = + buildAttributesCollection( + ruleContext.getRule().getAttributes(), + ruleContext, + attributeValueExtractorForRule(ruleContext)); } - artifactLabelMap = artifactLabelMapBuilder.build(); - outputsObject = - new SkylarkClassObject( - outputsBuilder, - "No attribute '%s' in outputs. Make sure you declared a rule output with this name."); - - ImmutableMap.Builder<String, Object> attrBuilder = new ImmutableMap.Builder<>(); - ImmutableMap.Builder<String, Object> executableBuilder = new ImmutableMap.Builder<>(); - ImmutableMap.Builder<Artifact, FilesToRunProvider> executableRunfilesbuilder = - new ImmutableMap.Builder<>(); - ImmutableMap.Builder<String, Object> fileBuilder = new ImmutableMap.Builder<>(); - ImmutableMap.Builder<String, Object> filesBuilder = new ImmutableMap.Builder<>(); - for (Attribute a : ruleContext.getRule().getAttributes()) { + + makeVariables = ruleContext.getConfigurationMakeVariableContext().collectMakeVariables(); + } + + private Function<Attribute, Object> attributeValueExtractorForRule( + final RuleContext ruleContext) { + return new Function<Attribute, Object>() { + @Nullable + @Override + public Object apply(Attribute attribute) { + return ruleContext.attributes().get(attribute.getName(), attribute.getType()); + } + }; + } + + private static SkylarkRuleAttributesCollection buildAttributesCollection( + Collection<Attribute> attributes, + RuleContext ruleContext, + Function<Attribute, Object> attributeValueExtractor) { + Builder<String, Object> attrBuilder = new Builder<>(); + Builder<String, Object> executableBuilder = new Builder<>(); + Builder<Artifact, FilesToRunProvider> executableRunfilesbuilder = new Builder<>(); + Builder<String, Object> fileBuilder = new Builder<>(); + Builder<String, Object> filesBuilder = new Builder<>(); + for (Attribute a : attributes) { Type<?> type = a.getType(); - Object val = ruleContext.attributes().get(a.getName(), type); + Object val = attributeValueExtractor.apply(a); if (type != BuildType.LABEL && type != BuildType.LABEL_LIST) { attrBuilder.put(a.getPublicName(), val == null ? Runtime.NONE // Attribute values should be type safe @@ -231,28 +322,77 @@ public final class SkylarkRuleContext { attrBuilder.put(skyname, new MutableList(allPrereq)); } } - attrObject = - new SkylarkClassObject( - attrBuilder.build(), - "No attribute '%s' in attr. Make sure you declared a rule attribute with this name."); - executableObject = - new SkylarkClassObject( - executableBuilder.build(), - "No attribute '%s' in executable. Make sure there is a label type attribute marked " - + "as 'executable' with this name"); - fileObject = - new SkylarkClassObject( - fileBuilder.build(), - "No attribute '%s' in file. Make sure there is a label type attribute marked " - + "as 'single_file' with this name"); - filesObject = - new SkylarkClassObject( - filesBuilder.build(), - "No attribute '%s' in files. Make sure there is a label or label_list type attribute " - + "with this name"); - executableRunfilesMap = executableRunfilesbuilder.build(); - makeVariables = ruleContext.getConfigurationMakeVariableContext().collectMakeVariables(); + return new SkylarkRuleAttributesCollection( + attrBuilder.build(), + executableBuilder.build(), + fileBuilder.build(), + filesBuilder.build(), + executableRunfilesbuilder.build()); + } + + @SkylarkModule( + name = "rule_attributes", + doc = "Information about attributes of a rule an aspect is applied to." + ) + private static class SkylarkRuleAttributesCollection { + private final SkylarkClassObject attrObject; + private final SkylarkClassObject executableObject; + private final SkylarkClassObject fileObject; + private final SkylarkClassObject filesObject; + private final ImmutableMap<Artifact, FilesToRunProvider> executableRunfilesMap; + + private SkylarkRuleAttributesCollection( + ImmutableMap<String, Object> attrs, + ImmutableMap<String, Object> executables, + ImmutableMap<String, Object> singleFiles, + ImmutableMap<String, Object> files, + ImmutableMap<Artifact, FilesToRunProvider> executableRunfilesMap) { + attrObject = + new SkylarkClassObject( + attrs, + "No attribute '%s' in attr. Make sure you declared a rule attribute with this name."); + executableObject = + new SkylarkClassObject( + executables, + "No attribute '%s' in executable. Make sure there is a label type attribute marked " + + "as 'executable' with this name"); + fileObject = + new SkylarkClassObject( + singleFiles, + "No attribute '%s' in file. Make sure there is a label type attribute marked " + + "as 'single_file' with this name"); + filesObject = + new SkylarkClassObject( + files, + "No attribute '%s' in files. Make sure there is a label or label_list type attribute " + + "with this name"); + this.executableRunfilesMap = executableRunfilesMap; + } + + @SkylarkCallable(name = "attr", structField = true, doc = ATTR_DOC) + public SkylarkClassObject getAttr() { + return attrObject; + } + + @SkylarkCallable(name = "executable", structField = true, doc = EXECUTABLE_DOC) + public SkylarkClassObject getExecutable() { + return executableObject; + } + + @SkylarkCallable(name = "file", structField = true, doc = FILE_DOC) + public SkylarkClassObject getFile() { + return fileObject; + } + + @SkylarkCallable(name = "files", structField = true, doc = FILES_DOC) + public SkylarkClassObject getFiles() { + return filesObject; + } + + public ImmutableMap<Artifact, FilesToRunProvider> getExecutableRunfilesMap() { + return executableRunfilesMap; + } } private void addOutput(HashMap<String, Object> outputsBuilder, String key, Object value) @@ -270,55 +410,33 @@ public final class SkylarkRuleContext { return ruleContext; } - @SkylarkCallable(name = "attr", structField = true, - doc = "A struct to access the values of the attributes. The values are provided by " - + "the user (if not, a default value is used).") + @SkylarkCallable(name = "attr", structField = true, doc = ATTR_DOC) public SkylarkClassObject getAttr() { - return attrObject; + return attributesCollection.getAttr(); } /** * <p>See {@link RuleContext#getExecutablePrerequisite(String, Mode)}. */ - @SkylarkCallable(name = "executable", structField = true, - doc = "A <code>struct</code> containing executable files defined in label type " - + "attributes marked as <code>executable=True</code>. The struct fields correspond " - + "to the attribute names. Each struct value is always a <code>file</code>s or " - + "<code>None</code>. If an optional attribute is not specified in the rule " - + "then the corresponding struct value is <code>None</code>. If a label type is not " - + "marked as <code>executable=True</code>, no corresponding struct field is generated.") + @SkylarkCallable(name = "executable", structField = true, doc = EXECUTABLE_DOC) public SkylarkClassObject getExecutable() { - return executableObject; + return attributesCollection.getExecutable(); } /** * See {@link RuleContext#getPrerequisiteArtifact(String, Mode)}. */ - @SkylarkCallable(name = "file", structField = true, - doc = "A <code>struct</code> containing files defined in label type " - + "attributes marked as <code>single_file=True</code>. The struct fields correspond " - + "to the attribute names. The struct value is always a <code>file</code> or " - + "<code>None</code>. If an optional attribute is not specified in the rule " - + "then the corresponding struct value is <code>None</code>. If a label type is not " - + "marked as <code>single_file=True</code>, no corresponding struct field is generated. " - + "It is a shortcut for:" - + "<pre class=language-python>list(ctx.attr.<ATTR>.files)[0]</pre>") + @SkylarkCallable(name = "file", structField = true, doc = FILE_DOC) public SkylarkClassObject getFile() { - return fileObject; + return attributesCollection.getFile(); } /** * See {@link RuleContext#getPrerequisiteArtifacts(String, Mode)}. */ - @SkylarkCallable(name = "files", structField = true, - doc = "A <code>struct</code> containing files defined in label or label list " - + "type attributes. The struct fields correspond to the attribute names. The struct " - + "values are <code>list</code> of <code>file</code>s. If an optional attribute is " - + "not specified in the rule, an empty list is generated." - + "It is a shortcut for:" - + "<pre class=language-python>[f for t in ctx.attr.<ATTR> for f in t.files]</pre>") + @SkylarkCallable(name = "files", structField = true, doc = FILES_DOC) public SkylarkClassObject getFiles() { - return filesObject; + return attributesCollection.getFiles(); } @SkylarkCallable(name = "workspace_name", structField = true, @@ -368,23 +486,22 @@ public final class SkylarkRuleContext { return ruleContext.getHostConfiguration(); } - @SkylarkCallable(structField = true, - doc = "A <code>struct</code> containing all the output files." - + " The struct is generated the following way:<br>" - + "<ul><li>If the rule is marked as <code>executable=True</code> the struct has an " - + "\"executable\" field with the rules default executable <code>file</code> value." - + "<li>For every entry in the rule's <code>outputs</code> dict an attr is generated with " - + "the same name and the corresponding <code>file</code> value." - + "<li>For every output type attribute a struct attribute is generated with the " - + "same name and the corresponding <code>file</code> value or <code>None</code>, " - + "if no value is specified in the rule." - + "<li>For every output list type attribute a struct attribute is generated with the " - + "same name and corresponding <code>list</code> of <code>file</code>s value " - + "(an empty list if no value is specified in the rule).</ul>") - public SkylarkClassObject outputs() { + @SkylarkCallable(structField = true, doc = OUTPUTS_DOC) + public SkylarkClassObject outputs() throws EvalException { + if (outputsObject == null) { + throw new EvalException(Location.BUILTIN, "'outputs' is not defined"); + } return outputsObject; } + @SkylarkCallable(structField = true, doc = "Returns rule attributes descriptor") + public SkylarkRuleAttributesCollection rule() throws EvalException { + if (ruleAttributesCollection == null) { + throw new EvalException(Location.BUILTIN, "'rule' is not defined"); + } + return ruleAttributesCollection; + } + @SkylarkCallable(structField = true, doc = "Dictionary (String to String) of configuration variables") public ImmutableMap<String, String> var() { @@ -407,16 +524,19 @@ public final class SkylarkRuleContext { return new MutableList(options); // no env is provided, so it's effectively immutable } - @SkylarkCallable(doc = - "Expands all references to labels embedded within a string for all files using a mapping " - + "from definition labels (i.e. the label in the output type attribute) to files. Deprecated.", - documented = false) - public String expand(@Nullable String expression, - SkylarkList artifacts, Label labelResolver) throws EvalException, FuncallException { + @SkylarkCallable( + doc = + "Expands all references to labels embedded within a string for all files using a mapping " + + "from definition labels (i.e. the label in the output type attribute) to files. " + + "Deprecated.", + documented = false + ) + public String expand(@Nullable String expression, SkylarkList artifacts, Label labelResolver) + throws EvalException, FuncallException { try { Map<Label, Iterable<Artifact>> labelMap = new HashMap<>(); for (Artifact artifact : artifacts.getContents(Artifact.class, "artifacts")) { - labelMap.put(artifactLabelMap.get(artifact), ImmutableList.of(artifact)); + labelMap.put(artifactsLabelMap.get(artifact), ImmutableList.of(artifact)); } return LabelExpander.expand(expression, labelMap, labelResolver); } catch (NotUniqueExpansionException e) { @@ -510,8 +630,9 @@ public final class SkylarkRuleContext { }); } + FilesToRunProvider getExecutableRunfiles(Artifact executable) { - return executableRunfilesMap.get(executable); + return attributesCollection.getExecutableRunfilesMap().get(executable); } @SkylarkCallable(name = "info_file", structField = true, documented = false, diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java index 794d746f1e..c2d489cf86 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java +++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java @@ -24,6 +24,7 @@ import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.packages.AspectParameters; import com.google.devtools.build.lib.rules.SkylarkRuleClassFunctions.SkylarkAspect; import com.google.devtools.build.lib.rules.SkylarkRuleContext; +import com.google.devtools.build.lib.rules.SkylarkRuleContext.Kind; import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject; import com.google.devtools.build.lib.syntax.Environment; import com.google.devtools.build.lib.syntax.EvalException; @@ -50,7 +51,7 @@ public class SkylarkAspectFactory implements ConfiguredAspectFactory { try (Mutability mutability = Mutability.create("aspect")) { SkylarkRuleContext skylarkRuleContext; try { - skylarkRuleContext = new SkylarkRuleContext(ruleContext); + skylarkRuleContext = new SkylarkRuleContext(ruleContext, Kind.ASPECT); } catch (EvalException e) { ruleContext.ruleError(e.getMessage()); return null; |