| // 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.skylark; |
| |
| import com.google.common.collect.ImmutableMap; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.analysis.AliasProvider; |
| import com.google.devtools.build.lib.analysis.FilesToRunProvider; |
| import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; |
| import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.packages.Attribute; |
| import com.google.devtools.build.lib.packages.BuildType; |
| import com.google.devtools.build.lib.packages.Info; |
| import com.google.devtools.build.lib.packages.NativeProvider; |
| import com.google.devtools.build.lib.skylarkbuildapi.SkylarkAttributesCollectionApi; |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; |
| import com.google.devtools.build.lib.syntax.Environment; |
| import com.google.devtools.build.lib.syntax.EvalException; |
| import com.google.devtools.build.lib.syntax.Runtime; |
| import com.google.devtools.build.lib.syntax.SkylarkList; |
| import com.google.devtools.build.lib.syntax.SkylarkType; |
| import com.google.devtools.build.lib.syntax.Type; |
| import com.google.devtools.build.lib.syntax.Type.LabelClass; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** Information about attributes of a rule an aspect is applied to. */ |
| class SkylarkAttributesCollection implements SkylarkAttributesCollectionApi { |
| private final SkylarkRuleContext skylarkRuleContext; |
| private final Info attrObject; |
| private final Info executableObject; |
| private final Info fileObject; |
| private final Info filesObject; |
| private final ImmutableMap<Artifact, FilesToRunProvider> executableRunfilesMap; |
| private final String ruleClassName; |
| |
| private SkylarkAttributesCollection( |
| SkylarkRuleContext skylarkRuleContext, |
| String ruleClassName, |
| Map<String, Object> attrs, |
| Map<String, Object> executables, |
| Map<String, Object> singleFiles, |
| Map<String, Object> files, |
| ImmutableMap<Artifact, FilesToRunProvider> executableRunfilesMap) { |
| this.skylarkRuleContext = skylarkRuleContext; |
| this.ruleClassName = ruleClassName; |
| attrObject = |
| NativeProvider.STRUCT.create( |
| attrs, |
| "No attribute '%s' in attr. Make sure you declared a rule attribute with this name."); |
| executableObject = |
| NativeProvider.STRUCT.create( |
| executables, |
| "No attribute '%s' in executable. Make sure there is a label type attribute marked " |
| + "as 'executable' with this name"); |
| fileObject = |
| NativeProvider.STRUCT.create( |
| singleFiles, |
| "No attribute '%s' in file. Make sure there is a label type attribute marked " |
| + "as 'single_file' with this name"); |
| filesObject = |
| NativeProvider.STRUCT.create( |
| files, |
| "No attribute '%s' in files. Make sure there is a label or label_list type attribute " |
| + "with this name"); |
| this.executableRunfilesMap = executableRunfilesMap; |
| } |
| |
| private void checkMutable(String attrName) throws EvalException { |
| skylarkRuleContext.checkMutable("rule." + attrName); |
| } |
| |
| @Override |
| public Info getAttr() throws EvalException { |
| checkMutable("attr"); |
| return attrObject; |
| } |
| |
| @Override |
| public Info getExecutable() throws EvalException { |
| checkMutable("executable"); |
| return executableObject; |
| } |
| |
| @Override |
| public Info getFile() throws EvalException { |
| checkMutable("file"); |
| return fileObject; |
| } |
| |
| @Override |
| public Info getFiles() throws EvalException { |
| checkMutable("files"); |
| return filesObject; |
| } |
| |
| @Override |
| public String getRuleClassName() throws EvalException { |
| checkMutable("kind"); |
| return ruleClassName; |
| } |
| |
| public ImmutableMap<Artifact, FilesToRunProvider> getExecutableRunfilesMap() { |
| return executableRunfilesMap; |
| } |
| |
| @Override |
| public boolean isImmutable() { |
| return skylarkRuleContext.isImmutable(); |
| } |
| |
| @Override |
| public void repr(SkylarkPrinter printer) { |
| printer.append("<rule collection for " + skylarkRuleContext.getRuleLabelCanonicalName() + ">"); |
| } |
| |
| public static Builder builder(SkylarkRuleContext ruleContext) { |
| return new Builder(ruleContext); |
| } |
| |
| public static class Builder { |
| private final SkylarkRuleContext context; |
| private final LinkedHashMap<String, Object> attrBuilder = new LinkedHashMap<>(); |
| private final LinkedHashMap<String, Object> executableBuilder = new LinkedHashMap<>(); |
| private final ImmutableMap.Builder<Artifact, FilesToRunProvider> executableRunfilesbuilder = |
| ImmutableMap.builder(); |
| private final LinkedHashMap<String, Object> fileBuilder = new LinkedHashMap<>(); |
| private final LinkedHashMap<String, Object> filesBuilder = new LinkedHashMap<>(); |
| private final HashSet<Artifact> seenExecutables = new HashSet<>(); |
| |
| private Builder(SkylarkRuleContext ruleContext) { |
| this.context = ruleContext; |
| } |
| |
| public void addAttribute(Attribute a, Object val) { |
| Type<?> type = a.getType(); |
| String skyname = a.getPublicName(); |
| |
| // The first attribute with the same name wins. |
| if (attrBuilder.containsKey(skyname)) { |
| return; |
| } |
| |
| // TODO(mstaib): Remove the LABEL_DICT_UNARY special case of this conditional |
| // LABEL_DICT_UNARY was previously not treated as a dependency-bearing type, and was put into |
| // Skylark as a Map<String, Label>; this special case preserves that behavior temporarily. |
| if (type.getLabelClass() != LabelClass.DEPENDENCY || type == BuildType.LABEL_DICT_UNARY) { |
| attrBuilder.put( |
| skyname, |
| val == null |
| ? Runtime.NONE |
| // Attribute values should be type safe |
| : SkylarkType.convertToSkylark(val, (Environment) null)); |
| return; |
| } |
| if (a.isExecutable()) { |
| // In Skylark only label (not label list) type attributes can have the Executable flag. |
| FilesToRunProvider provider = |
| context.getRuleContext().getExecutablePrerequisite(a.getName(), Mode.DONT_CHECK); |
| if (provider != null && provider.getExecutable() != null) { |
| Artifact executable = provider.getExecutable(); |
| executableBuilder.put(skyname, executable); |
| if (!seenExecutables.contains(executable)) { |
| // todo(dslomov,laurentlb): In general, this is incorrect. |
| // We associate the first encountered FilesToRunProvider with |
| // the executable (this provider is later used to build the spawn). |
| // However ideally we should associate a provider with the attribute name, |
| // and pass the correct FilesToRunProvider to the spawn depending on |
| // what attribute is used to access the executable. |
| executableRunfilesbuilder.put(executable, provider); |
| seenExecutables.add(executable); |
| } |
| } else { |
| executableBuilder.put(skyname, Runtime.NONE); |
| } |
| } |
| if (a.isSingleArtifact()) { |
| // In Skylark only label (not label list) type attributes can have the SingleArtifact flag. |
| Artifact artifact = |
| context.getRuleContext().getPrerequisiteArtifact(a.getName(), Mode.DONT_CHECK); |
| if (artifact != null) { |
| fileBuilder.put(skyname, artifact); |
| } else { |
| fileBuilder.put(skyname, Runtime.NONE); |
| } |
| } |
| filesBuilder.put( |
| skyname, |
| context.getRuleContext().getPrerequisiteArtifacts(a.getName(), Mode.DONT_CHECK).list()); |
| |
| if (type == BuildType.LABEL && !a.hasSplitConfigurationTransition()) { |
| Object prereq = context.getRuleContext().getPrerequisite(a.getName(), Mode.DONT_CHECK); |
| if (prereq == null) { |
| prereq = Runtime.NONE; |
| } |
| attrBuilder.put(skyname, prereq); |
| } else if (type == BuildType.LABEL_LIST |
| || (type == BuildType.LABEL && a.hasSplitConfigurationTransition())) { |
| List<?> allPrereq = context.getRuleContext().getPrerequisites(a.getName(), Mode.DONT_CHECK); |
| attrBuilder.put(skyname, SkylarkList.createImmutable(allPrereq)); |
| } else if (type == BuildType.LABEL_KEYED_STRING_DICT) { |
| ImmutableMap.Builder<TransitiveInfoCollection, String> builder = ImmutableMap.builder(); |
| Map<Label, String> original = BuildType.LABEL_KEYED_STRING_DICT.cast(val); |
| List<? extends TransitiveInfoCollection> allPrereq = |
| context.getRuleContext().getPrerequisites(a.getName(), Mode.DONT_CHECK); |
| for (TransitiveInfoCollection prereq : allPrereq) { |
| builder.put(prereq, original.get(AliasProvider.getDependencyLabel(prereq))); |
| } |
| attrBuilder.put(skyname, SkylarkType.convertToSkylark(builder.build(), (Environment) null)); |
| } else if (type == BuildType.LABEL_DICT_UNARY) { |
| Map<Label, TransitiveInfoCollection> prereqsByLabel = new LinkedHashMap<>(); |
| for (TransitiveInfoCollection target : |
| context.getRuleContext().getPrerequisites(a.getName(), Mode.DONT_CHECK)) { |
| prereqsByLabel.put(target.getLabel(), target); |
| } |
| ImmutableMap.Builder<String, TransitiveInfoCollection> attrValue = ImmutableMap.builder(); |
| for (Map.Entry<String, Label> entry : ((Map<String, Label>) val).entrySet()) { |
| attrValue.put(entry.getKey(), prereqsByLabel.get(entry.getValue())); |
| } |
| attrBuilder.put(skyname, attrValue.build()); |
| } else { |
| throw new IllegalArgumentException( |
| "Can't transform attribute " |
| + a.getName() |
| + " of type " |
| + type |
| + " to a Skylark object"); |
| } |
| } |
| |
| public SkylarkAttributesCollection build() { |
| return new SkylarkAttributesCollection( |
| context, |
| context.getRuleContext().getRule().getRuleClass(), |
| attrBuilder, |
| executableBuilder, |
| fileBuilder, |
| filesBuilder, |
| executableRunfilesbuilder.build()); |
| } |
| } |
| } |