// 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.StructProvider; 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 executableRunfilesMap; private final String ruleClassName; private SkylarkAttributesCollection( SkylarkRuleContext skylarkRuleContext, String ruleClassName, Map attrs, Map executables, Map singleFiles, Map files, ImmutableMap executableRunfilesMap) { this.skylarkRuleContext = skylarkRuleContext; this.ruleClassName = ruleClassName; attrObject = StructProvider.STRUCT.create( attrs, "No attribute '%s' in attr. Make sure you declared a rule attribute with this name."); executableObject = StructProvider.STRUCT.create( executables, "No attribute '%s' in executable. Make sure there is a label type attribute marked " + "as 'executable' with this name"); fileObject = StructProvider.STRUCT.create( singleFiles, "No attribute '%s' in file. Make sure there is a label type attribute marked " + "as 'single_file' with this name"); filesObject = StructProvider.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 getExecutableRunfilesMap() { return executableRunfilesMap; } @Override public boolean isImmutable() { return skylarkRuleContext.isImmutable(); } @Override public void repr(SkylarkPrinter printer) { printer.append(""); } public static Builder builder(SkylarkRuleContext ruleContext) { return new Builder(ruleContext); } public static class Builder { private final SkylarkRuleContext context; private final LinkedHashMap attrBuilder = new LinkedHashMap<>(); private final LinkedHashMap executableBuilder = new LinkedHashMap<>(); private final ImmutableMap.Builder executableRunfilesbuilder = ImmutableMap.builder(); private final LinkedHashMap fileBuilder = new LinkedHashMap<>(); private final LinkedHashMap filesBuilder = new LinkedHashMap<>(); private final HashSet 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; 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 builder = ImmutableMap.builder(); Map original = BuildType.LABEL_KEYED_STRING_DICT.cast(val); List 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 prereqsByLabel = new LinkedHashMap<>(); for (TransitiveInfoCollection target : context.getRuleContext().getPrerequisites(a.getName(), Mode.DONT_CHECK)) { prereqsByLabel.put(target.getLabel(), target); } ImmutableMap.Builder attrValue = ImmutableMap.builder(); for (Map.Entry entry : ((Map) 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()); } } }