// 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.analysis.skylark; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Ordering; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.ArtifactRoot; import com.google.devtools.build.lib.analysis.ActionsProvider; import com.google.devtools.build.lib.analysis.AliasProvider; import com.google.devtools.build.lib.analysis.CommandHelper; import com.google.devtools.build.lib.analysis.ConfigurationMakeVariableContext; import com.google.devtools.build.lib.analysis.DefaultInfo; import com.google.devtools.build.lib.analysis.FileProvider; import com.google.devtools.build.lib.analysis.FilesToRunProvider; import com.google.devtools.build.lib.analysis.LabelExpander; import com.google.devtools.build.lib.analysis.LabelExpander.NotUniqueExpansionException; import com.google.devtools.build.lib.analysis.LocationExpander; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.Runfiles; import com.google.devtools.build.lib.analysis.RunfilesProvider; import com.google.devtools.build.lib.analysis.ShToolchain; import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.FragmentCollection; import com.google.devtools.build.lib.analysis.config.HostTransition; import com.google.devtools.build.lib.analysis.config.transitions.NoTransition; import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.analysis.stringtemplate.ExpansionException; import com.google.devtools.build.lib.analysis.test.InstrumentedFilesCollector; import com.google.devtools.build.lib.analysis.test.InstrumentedFilesProvider; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.packages.Aspect; import com.google.devtools.build.lib.packages.AspectDescriptor; import com.google.devtools.build.lib.packages.Attribute; import com.google.devtools.build.lib.packages.BuildType; import com.google.devtools.build.lib.packages.ImplicitOutputsFunction; import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SkylarkImplicitOutputsFunction; import com.google.devtools.build.lib.packages.Info; import com.google.devtools.build.lib.packages.OutputFile; import com.google.devtools.build.lib.packages.Package; import com.google.devtools.build.lib.packages.Provider; import com.google.devtools.build.lib.packages.RawAttributeMapper; import com.google.devtools.build.lib.packages.StructProvider; import com.google.devtools.build.lib.shell.ShellUtils; import com.google.devtools.build.lib.shell.ShellUtils.TokenizationException; import com.google.devtools.build.lib.skylarkbuildapi.FileApi; import com.google.devtools.build.lib.skylarkbuildapi.FileRootApi; import com.google.devtools.build.lib.skylarkbuildapi.SkylarkRuleContextApi; import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; import com.google.devtools.build.lib.skylarkinterface.SkylarkValue; import com.google.devtools.build.lib.syntax.ClassObject; import com.google.devtools.build.lib.syntax.Environment; import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.EvalUtils; import com.google.devtools.build.lib.syntax.FuncallExpression.FuncallException; import com.google.devtools.build.lib.syntax.Printer; import com.google.devtools.build.lib.syntax.Runtime; import com.google.devtools.build.lib.syntax.SkylarkDict; import com.google.devtools.build.lib.syntax.SkylarkIndexable; import com.google.devtools.build.lib.syntax.SkylarkList; import com.google.devtools.build.lib.syntax.SkylarkList.MutableList; import com.google.devtools.build.lib.syntax.SkylarkList.Tuple; import com.google.devtools.build.lib.syntax.SkylarkNestedSet; import com.google.devtools.build.lib.syntax.SkylarkSemantics; import com.google.devtools.build.lib.syntax.Type; import com.google.devtools.build.lib.syntax.Type.ConversionException; import com.google.devtools.build.lib.syntax.Type.LabelClass; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * A Skylark API for the ruleContext. * *

"This object becomes featureless once the rule implementation function that it was created for * has completed. To achieve this, the {@link #nullify()} should be called once the evaluation of * the function is completed. The method both frees memory by deleting all significant fields of the * object and makes it impossible to accidentally use this object where it's not supposed to be used * (such attempts will result in {@link EvalException}s). */ public final class SkylarkRuleContext implements SkylarkRuleContextApi { public static final Function ATTRIBUTE_VALUE_EXTRACTOR_FOR_ASPECT = new Function() { @Nullable @Override public Object apply(Attribute attribute) { return attribute.getDefaultValue(null); } }; public static final String EXECUTABLE_OUTPUT_NAME = "executable"; // This field is a copy of the info from ruleContext, stored separately so it can be accessed // after this object has been nullified. private final String ruleLabelCanonicalName; private final boolean isForAspect; private final SkylarkActionFactory actionFactory; // The fields below intended to be final except that they can be cleared by calling `nullify()` // when the object becomes featureless. private RuleContext ruleContext; private FragmentCollection fragments; private FragmentCollection hostFragments; private AspectDescriptor aspectDescriptor; private final SkylarkSemantics skylarkSemantics; private SkylarkDict makeVariables; private SkylarkAttributesCollection attributesCollection; private SkylarkAttributesCollection ruleAttributesCollection; private Info splitAttributes; // TODO(bazel-team): we only need this because of the css_binary rule. private ImmutableMap artifactsLabelMap; private Outputs outputsObject; /** * Creates a new SkylarkRuleContext using ruleContext. * @param aspectDescriptor aspect for which the context is created, or null * if it is for a rule. * @throws InterruptedException */ public SkylarkRuleContext(RuleContext ruleContext, @Nullable AspectDescriptor aspectDescriptor, SkylarkSemantics skylarkSemantics) throws EvalException, InterruptedException { this.actionFactory = new SkylarkActionFactory(this, skylarkSemantics, ruleContext); this.ruleContext = Preconditions.checkNotNull(ruleContext); this.ruleLabelCanonicalName = ruleContext.getLabel().getCanonicalForm(); this.fragments = new FragmentCollection(ruleContext, NoTransition.INSTANCE); this.hostFragments = new FragmentCollection(ruleContext, HostTransition.INSTANCE); this.aspectDescriptor = aspectDescriptor; this.skylarkSemantics = skylarkSemantics; if (aspectDescriptor == null) { this.isForAspect = false; Collection attributes = ruleContext.getRule().getAttributes(); Outputs outputs = new Outputs(this); ImplicitOutputsFunction implicitOutputsFunction = ruleContext.getRule().getImplicitOutputsFunction(); if (implicitOutputsFunction instanceof SkylarkImplicitOutputsFunction) { SkylarkImplicitOutputsFunction func = (SkylarkImplicitOutputsFunction) implicitOutputsFunction; for (Map.Entry entry : func.calculateOutputs( ruleContext.getAnalysisEnvironment().getEventHandler(), RawAttributeMapper.of(ruleContext.getRule())) .entrySet()) { outputs.addOutput( entry.getKey(), ruleContext.getImplicitOutputArtifact(entry.getValue())); } } ImmutableMap.Builder artifactLabelMapBuilder = ImmutableMap.builder(); for (Attribute a : attributes) { String attrName = a.getName(); Type type = a.getType(); if (type.getLabelClass() != LabelClass.OUTPUT) { continue; } ImmutableList.Builder 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 artifacts = artifactsBuilder.build(); if (type == BuildType.OUTPUT) { if (artifacts.size() == 1) { outputs.addOutput(attrName, Iterables.getOnlyElement(artifacts)); } else { outputs.addOutput(attrName, Runtime.NONE); } } else if (type == BuildType.OUTPUT_LIST) { outputs.addOutput(attrName, SkylarkList.createImmutable(artifacts)); } else { throw new IllegalArgumentException( "Type of " + attrName + "(" + type + ") is not output type "); } } this.artifactsLabelMap = artifactLabelMapBuilder.build(); this.outputsObject = outputs; SkylarkAttributesCollection.Builder builder = SkylarkAttributesCollection.builder(this); for (Attribute attribute : ruleContext.getRule().getAttributes()) { Object value = ruleContext.attributes().get(attribute.getName(), attribute.getType()); builder.addAttribute(attribute, value); } this.attributesCollection = builder.build(); this.splitAttributes = buildSplitAttributeInfo(attributes, ruleContext); this.ruleAttributesCollection = null; } else { // ASPECT this.isForAspect = true; this.artifactsLabelMap = ImmutableMap.of(); this.outputsObject = null; ImmutableCollection attributes = ruleContext.getMainAspect().getDefinition().getAttributes().values(); SkylarkAttributesCollection.Builder aspectBuilder = SkylarkAttributesCollection.builder(this); for (Attribute attribute : attributes) { aspectBuilder.addAttribute(attribute, attribute.getDefaultValue(null)); } this.attributesCollection = aspectBuilder.build(); this.splitAttributes = null; SkylarkAttributesCollection.Builder ruleBuilder = SkylarkAttributesCollection.builder(this); for (Attribute attribute : ruleContext.getRule().getAttributes()) { Object value = ruleContext.attributes().get(attribute.getName(), attribute.getType()); ruleBuilder.addAttribute(attribute, value); } for (Aspect aspect : ruleContext.getAspects()) { if (aspect.equals(ruleContext.getMainAspect())) { // Aspect's own attributes are in attributesCollection. continue; } for (Attribute attribute : aspect.getDefinition().getAttributes().values()) { ruleBuilder.addAttribute(attribute, attribute.getDefaultValue(null)); } } this.ruleAttributesCollection = ruleBuilder.build(); } makeVariables = ruleContext.getConfigurationMakeVariableContext().collectMakeVariables(); } /** * Represents `ctx.outputs`. * *

A {@link ClassObject} (struct-like data structure) with "executable" field created * lazily on-demand. * *

Note: There is only one {@code Outputs} object per rule context, so default * (object identity) equals and hashCode suffice. */ private static class Outputs implements ClassObject, SkylarkValue { private final Map outputs; private final SkylarkRuleContext context; private boolean executableCreated = false; public Outputs(SkylarkRuleContext context) { this.outputs = new LinkedHashMap<>(); this.context = context; } private void addOutput(String key, Object value) throws EvalException { Preconditions.checkState(!context.isImmutable(), "Cannot add outputs to immutable Outputs object"); if (outputs.containsKey(key) || (context.isExecutable() && EXECUTABLE_OUTPUT_NAME.equals(key))) { throw new EvalException(null, "Multiple outputs with the same key: " + key); } outputs.put(key, value); } @Override public boolean isImmutable() { return context.isImmutable(); } @Override public ImmutableCollection getFieldNames() throws EvalException { checkMutable(); ImmutableList.Builder result = ImmutableList.builder(); if (context.isExecutable() && executableCreated) { result.add(EXECUTABLE_OUTPUT_NAME); } result.addAll(outputs.keySet()); return result.build(); } @Nullable @Override public Object getValue(String name) throws EvalException { checkMutable(); if (context.isExecutable() && EXECUTABLE_OUTPUT_NAME.equals(name)) { executableCreated = true; // createOutputArtifact() will cache the created artifact. return context.getRuleContext().createOutputArtifact(); } return outputs.get(name); } @Nullable @Override public String getErrorMessageForUnknownField(String name) { return String.format( "No attribute '%s' in outputs. Make sure you declared a rule output with this name.", name); } @Override public void repr(SkylarkPrinter printer) { if (isImmutable()) { printer.append("ctx.outputs(for "); printer.append(context.ruleLabelCanonicalName); printer.append(")"); return; } boolean first = true; printer.append("ctx.outputs("); // Sort by field name to ensure deterministic output. try { for (String field : Ordering.natural().sortedCopy(getFieldNames())) { if (!first) { printer.append(", "); } first = false; printer.append(field); printer.append(" = "); printer.repr(getValue(field)); } printer.append(")"); } catch (EvalException e) { throw new AssertionError("mutable ctx.outputs should not throw", e); } } private void checkMutable() throws EvalException { if (isImmutable()) { throw new EvalException( null, String.format( "cannot access outputs of rule '%s' outside of its own " + "rule implementation function", context.ruleLabelCanonicalName)); } } } public boolean isExecutable() { return ruleContext.getRule().getRuleClassObject().isExecutableSkylark(); } public boolean isDefaultExecutableCreated() { return this.outputsObject.executableCreated; } /** * Nullifies fields of the object when it's not supposed to be used anymore to free unused memory * and to make sure this object is not accessed when it's not supposed to (after the corresponding * rule implementation function has exited). */ public void nullify() { actionFactory.nullify(); ruleContext = null; fragments = null; hostFragments = null; aspectDescriptor = null; makeVariables = null; attributesCollection = null; ruleAttributesCollection = null; splitAttributes = null; artifactsLabelMap = null; outputsObject = null; } public void checkMutable(String attrName) throws EvalException { if (isImmutable()) { throw new EvalException(null, String.format( "cannot access field or method '%s' of rule context for '%s' outside of its own rule " + "implementation function", attrName, ruleLabelCanonicalName)); } } @Nullable public AspectDescriptor getAspectDescriptor() { return aspectDescriptor; } public String getRuleLabelCanonicalName() { return ruleLabelCanonicalName; } private static Info buildSplitAttributeInfo( Collection attributes, RuleContext ruleContext) { ImmutableMap.Builder splitAttrInfos = ImmutableMap.builder(); for (Attribute attr : attributes) { if (attr.hasSplitConfigurationTransition()) { Map, ? extends List> splitPrereqs = ruleContext.getSplitPrerequisites(attr.getName()); Map splitPrereqsMap = new LinkedHashMap<>(); for (Map.Entry, ? extends List> splitPrereq : splitPrereqs.entrySet()) { Object value; if (attr.getType() == BuildType.LABEL) { Preconditions.checkState(splitPrereq.getValue().size() == 1); value = splitPrereq.getValue().get(0); } else { // BuildType.LABEL_LIST value = SkylarkList.createImmutable(splitPrereq.getValue()); } if (splitPrereq.getKey().isPresent()) { splitPrereqsMap.put(splitPrereq.getKey().get(), value); } else { // If the split transition is not in effect, then the key will be missing since there's // nothing to key on because the dependencies aren't split and getSplitPrerequisites() // behaves like getPrerequisites(). This also means there should be only one entry in // the map. Use None in Skylark to represent this. Preconditions.checkState(splitPrereqs.size() == 1); splitPrereqsMap.put(Runtime.NONE, value); } } splitAttrInfos.put(attr.getPublicName(), SkylarkDict.copyOf(null, splitPrereqsMap)); } } return StructProvider.STRUCT.create( splitAttrInfos.build(), "No attribute '%s' in split_attr. Make sure that this attribute is defined with a " + "split configuration."); } @Override public boolean isImmutable() { return ruleContext == null; } @Override public void repr(SkylarkPrinter printer) { if (isForAspect) { printer.append(""); } else { printer.append(""); } } /** * Returns the original ruleContext. */ public RuleContext getRuleContext() { return ruleContext; } @Override public Provider getDefaultProvider() { return DefaultInfo.PROVIDER; } @Override public SkylarkActionFactory actions() { return actionFactory; } @Override public SkylarkValue createdActions() throws EvalException { checkMutable("created_actions"); if (ruleContext.getRule().getRuleClassObject().isSkylarkTestable()) { return ActionsProvider.create( ruleContext.getAnalysisEnvironment().getRegisteredActions()); } else { return Runtime.NONE; } } @Override public Info getAttr() throws EvalException { checkMutable("attr"); return attributesCollection.getAttr(); } @Override public Info getSplitAttr() throws EvalException { checkMutable("split_attr"); if (splitAttributes == null) { throw new EvalException( Location.BUILTIN, "'split_attr' is available only in rule implementations"); } return splitAttributes; } /** See {@link RuleContext#getExecutablePrerequisite(String, Mode)}. */ @Override public Info getExecutable() throws EvalException { checkMutable("executable"); return attributesCollection.getExecutable(); } /** See {@link RuleContext#getPrerequisiteArtifact(String, Mode)}. */ @Override public Info getFile() throws EvalException { checkMutable("file"); return attributesCollection.getFile(); } /** See {@link RuleContext#getPrerequisiteArtifacts(String, Mode)}. */ @Override public Info getFiles() throws EvalException { checkMutable("files"); return attributesCollection.getFiles(); } @Override public String getWorkspaceName() throws EvalException { checkMutable("workspace_name"); return ruleContext.getWorkspaceName(); } @Override public Label getLabel() throws EvalException { checkMutable("label"); return ruleContext.getLabel(); } @Override public FragmentCollection getFragments() throws EvalException { checkMutable("fragments"); return fragments; } @Override public FragmentCollection getHostFragments() throws EvalException { checkMutable("host_fragments"); return hostFragments; } @Override public BuildConfiguration getConfiguration() throws EvalException { checkMutable("configuration"); return ruleContext.getConfiguration(); } @Override public BuildConfiguration getHostConfiguration() throws EvalException { checkMutable("host_configuration"); return ruleContext.getHostConfiguration(); } @Override public boolean instrumentCoverage(Object targetUnchecked) throws EvalException { checkMutable("coverage_instrumented"); BuildConfiguration config = ruleContext.getConfiguration(); if (!config.isCodeCoverageEnabled()) { return false; } if (targetUnchecked == Runtime.NONE) { return InstrumentedFilesCollector.shouldIncludeLocalSources(ruleContext); } TransitiveInfoCollection target = (TransitiveInfoCollection) targetUnchecked; return (target.getProvider(InstrumentedFilesProvider.class) != null) && InstrumentedFilesCollector.shouldIncludeLocalSources(config, target); } @Override public ImmutableList getFeatures() throws EvalException { checkMutable("features"); return ImmutableList.copyOf(ruleContext.getFeatures()); } @Override public ImmutableList getDisabledFeatures() throws EvalException { checkMutable("disabled_features"); return ImmutableList.copyOf(ruleContext.getDisabledFeatures()); } @Override public ArtifactRoot getBinDirectory() throws EvalException { checkMutable("bin_dir"); return getConfiguration().getBinDirectory(ruleContext.getRule().getRepository()); } @Override public ArtifactRoot getGenfilesDirectory() throws EvalException { checkMutable("genfiles_dir"); return getConfiguration().getGenfilesDirectory(ruleContext.getRule().getRepository()); } @Override public ClassObject outputs() throws EvalException { checkMutable("outputs"); if (outputsObject == null) { throw new EvalException(Location.BUILTIN, "'outputs' is not defined"); } return outputsObject; } @Override public SkylarkAttributesCollection rule() throws EvalException { checkMutable("rule"); if (!isForAspect) { throw new EvalException( Location.BUILTIN, "'rule' is only available in aspect implementations"); } return ruleAttributesCollection; } @Override public ImmutableList aspectIds() throws EvalException { checkMutable("aspect_ids"); if (!isForAspect) { throw new EvalException( Location.BUILTIN, "'aspect_ids' is only available in aspect implementations"); } ImmutableList.Builder result = ImmutableList.builder(); for (AspectDescriptor descriptor : ruleContext.getAspectDescriptors()) { result.add(descriptor.getDescription()); } return result.build(); } @Override public SkylarkDict var() throws EvalException { checkMutable("var"); return makeVariables; } @Override public SkylarkIndexable toolchains() throws EvalException { checkMutable("toolchains"); return ruleContext.getToolchainContext(); } @Override public String toString() { return ruleLabelCanonicalName; } @Override public SkylarkList tokenize(String optionString) throws FuncallException, EvalException { checkMutable("tokenize"); List options = new ArrayList<>(); try { ShellUtils.tokenize(options, optionString); } catch (TokenizationException e) { throw new FuncallException(e.getMessage() + " while tokenizing '" + optionString + "'"); } return SkylarkList.createImmutable(options); } @Override public String expand( @Nullable String expression, SkylarkList artifacts, Label labelResolver) throws EvalException, FuncallException { checkMutable("expand"); try { Map> labelMap = new HashMap<>(); for (Artifact artifact : artifacts.getContents(Artifact.class, "artifacts")) { labelMap.put(artifactsLabelMap.get(artifact), ImmutableList.of(artifact)); } return LabelExpander.expand(expression, labelMap, labelResolver); } catch (NotUniqueExpansionException e) { throw new FuncallException(e.getMessage() + " while expanding '" + expression + "'"); } } boolean isForAspect() { return isForAspect; } @Override public Artifact newFileFromFilename(String filename) throws EvalException { checkDeprecated("ctx.actions.declare_file", "ctx.new_file", null, skylarkSemantics); checkMutable("new_file"); return actionFactory.declareFile(filename, Runtime.NONE); } // Kept for compatibility with old code. @Override public Artifact newFileFromRoot(FileRootApi root, String filename) throws EvalException { checkMutable("new_file"); return ruleContext.getPackageRelativeArtifact(filename, (ArtifactRoot) root); } @Override public Artifact newFileFromBaseFile(FileApi baseArtifact, String newBaseName) throws EvalException { checkDeprecated("ctx.actions.declare_file", "ctx.new_file", null, skylarkSemantics); checkMutable("new_file"); return actionFactory.declareFile(newBaseName, baseArtifact); } // Kept for compatibility with old code. @Override public Artifact newFileFromRootAndBase(FileRootApi root, FileApi baseArtifact, String suffix) throws EvalException { checkMutable("new_file"); PathFragment original = ((Artifact) baseArtifact).getRootRelativePath(); PathFragment fragment = original.replaceName(original.getBaseName() + suffix); return ruleContext.getDerivedArtifact(fragment, (ArtifactRoot) root); } @Override public Artifact newDirectory(String name, Object siblingArtifactUnchecked) throws EvalException { checkDeprecated( "ctx.actions.declare_directory", "ctx.experimental_new_directory", null, skylarkSemantics); checkMutable("experimental_new_directory"); return actionFactory.declareDirectory(name, siblingArtifactUnchecked); } @Override public boolean checkPlaceholders(String template, SkylarkList allowedPlaceholders) throws EvalException { checkMutable("check_placeholders"); List actualPlaceHolders = new LinkedList<>(); Set allowedPlaceholderSet = ImmutableSet.copyOf(allowedPlaceholders.getContents(String.class, "allowed_placeholders")); ImplicitOutputsFunction.createPlaceholderSubstitutionFormatString(template, actualPlaceHolders); for (String placeholder : actualPlaceHolders) { if (!allowedPlaceholderSet.contains(placeholder)) { return false; } } return true; } @Override public String expandMakeVariables( String attributeName, String command, final Map additionalSubstitutions) throws EvalException { checkMutable("expand_make_variables"); ConfigurationMakeVariableContext makeVariableContext = new ConfigurationMakeVariableContext( this.getRuleContext(), ruleContext.getRule().getPackage(), ruleContext.getConfiguration(), ImmutableList.of()) { @Override public String lookupVariable(String variableName) throws ExpansionException { if (additionalSubstitutions.containsKey(variableName)) { return additionalSubstitutions.get(variableName); } else { return super.lookupVariable(variableName); } } }; return ruleContext.getExpander(makeVariableContext).expand(attributeName, command); } FilesToRunProvider getExecutableRunfiles(Artifact executable) { return attributesCollection.getExecutableRunfilesMap().get(executable); } @Override public Artifact getStableWorkspaceStatus() throws InterruptedException, EvalException { checkMutable("info_file"); return ruleContext.getAnalysisEnvironment().getStableWorkspaceStatusArtifact(); } @Override public Artifact getVolatileWorkspaceStatus() throws InterruptedException, EvalException { checkMutable("version_file"); return ruleContext.getAnalysisEnvironment().getVolatileWorkspaceStatusArtifact(); } @Override public String getBuildFileRelativePath() throws EvalException { checkMutable("build_file_path"); Package pkg = ruleContext.getRule().getPackage(); return pkg.getSourceRoot().relativize(pkg.getBuildFile().getPath()).getPathString(); } /** * A Skylark built-in function to create and register a SpawnAction using a dictionary of * parameters: action( inputs = [input1, input2, ...], outputs = [output1, output2, ...], * executable = executable, arguments = [argument1, argument2, ...], mnemonic = 'Mnemonic', * command = 'command', ) */ @Override public Runtime.NoneType action( SkylarkList outputs, Object inputs, Object executableUnchecked, Object toolsUnchecked, Object arguments, Object mnemonicUnchecked, Object commandUnchecked, Object progressMessage, Boolean useDefaultShellEnv, Object envUnchecked, Object executionRequirementsUnchecked, Object inputManifestsUnchecked, Location loc, Environment env) throws EvalException { checkDeprecated( "ctx.actions.run or ctx.actions.run_shell", "ctx.action", loc, env.getSemantics()); checkMutable("action"); if ((commandUnchecked == Runtime.NONE) == (executableUnchecked == Runtime.NONE)) { throw new EvalException(loc, "You must specify either 'command' or 'executable' argument"); } boolean hasCommand = commandUnchecked != Runtime.NONE; if (!hasCommand) { actions() .run( outputs, inputs, executableUnchecked, toolsUnchecked, arguments, mnemonicUnchecked, progressMessage, useDefaultShellEnv, envUnchecked, executionRequirementsUnchecked, inputManifestsUnchecked, loc); } else { actions() .runShell( outputs, inputs, toolsUnchecked, arguments, mnemonicUnchecked, commandUnchecked, progressMessage, useDefaultShellEnv, envUnchecked, executionRequirementsUnchecked, inputManifestsUnchecked, loc); } return Runtime.NONE; } @Override public String expandLocation(String input, SkylarkList targets, Location loc, Environment env) throws EvalException { checkMutable("expand_location"); try { return LocationExpander.withExecPaths( getRuleContext(), makeLabelMap(targets.getContents(TransitiveInfoCollection.class, "targets"))) .expand(input); } catch (IllegalStateException ise) { throw new EvalException(loc, ise); } } @Override public Runtime.NoneType fileAction( FileApi output, String content, Boolean executable, Location loc, Environment env) throws EvalException { checkDeprecated("ctx.actions.write", "ctx.file_action", loc, env.getSemantics()); checkMutable("file_action"); actions().write(output, content, executable); return Runtime.NONE; } @Override public Runtime.NoneType emptyAction(String mnemonic, Object inputs, Location loc, Environment env) throws EvalException { checkDeprecated("ctx.actions.do_nothing", "ctx.empty_action", loc, env.getSemantics()); checkMutable("empty_action"); actions().doNothing(mnemonic, inputs); return Runtime.NONE; } @Override public Runtime.NoneType templateAction( FileApi template, FileApi output, SkylarkDict substitutionsUnchecked, Boolean executable, Location loc, Environment env) throws EvalException { checkDeprecated("ctx.actions.expand_template", "ctx.template_action", loc, env.getSemantics()); checkMutable("template_action"); actions().expandTemplate(template, output, substitutionsUnchecked, executable); return Runtime.NONE; } @Override public Runfiles runfiles( SkylarkList files, Object transitiveFiles, Boolean collectData, Boolean collectDefault, SkylarkDict symlinks, SkylarkDict rootSymlinks, Location loc) throws EvalException, ConversionException { checkMutable("runfiles"); Runfiles.Builder builder = new Runfiles.Builder( getRuleContext().getWorkspaceName(), getConfiguration().legacyExternalRunfiles()); boolean checkConflicts = false; if (EvalUtils.toBoolean(collectData)) { builder.addRunfiles(getRuleContext(), RunfilesProvider.DATA_RUNFILES); } if (EvalUtils.toBoolean(collectDefault)) { builder.addRunfiles(getRuleContext(), RunfilesProvider.DEFAULT_RUNFILES); } if (!files.isEmpty()) { builder.addArtifacts(files.getContents(Artifact.class, "files")); } if (transitiveFiles != Runtime.NONE) { builder.addTransitiveArtifacts(((SkylarkNestedSet) transitiveFiles).getSet(Artifact.class)); } if (!symlinks.isEmpty()) { // If Skylark code directly manipulates symlinks, activate more stringent validity checking. checkConflicts = true; for (Map.Entry entry : symlinks.getContents(String.class, Artifact.class, "symlinks").entrySet()) { builder.addSymlink(PathFragment.create(entry.getKey()), entry.getValue()); } } if (!rootSymlinks.isEmpty()) { checkConflicts = true; for (Map.Entry entry : rootSymlinks.getContents(String.class, Artifact.class, "root_symlinks").entrySet()) { builder.addRootSymlink(PathFragment.create(entry.getKey()), entry.getValue()); } } Runfiles runfiles = builder.build(); if (checkConflicts) { runfiles.setConflictPolicy(Runfiles.ConflictPolicy.ERROR); } return runfiles; } @Override public Tuple resolveCommand( String command, Object attributeUnchecked, Boolean expandLocations, Object makeVariablesUnchecked, SkylarkList tools, SkylarkDict labelDictUnchecked, SkylarkDict executionRequirementsUnchecked, Location loc, Environment env) throws ConversionException, EvalException { checkMutable("resolve_command"); Label ruleLabel = getLabel(); Map> labelDict = checkLabelDict(labelDictUnchecked, loc, env); // The best way to fix this probably is to convert CommandHelper to Skylark. CommandHelper helper = new CommandHelper( getRuleContext(), tools.getContents(TransitiveInfoCollection.class, "tools"), ImmutableMap.copyOf(labelDict)); String attribute = Type.STRING.convertOptional(attributeUnchecked, "attribute", ruleLabel); if (expandLocations) { command = helper.resolveCommandAndExpandLabels(command, attribute, /*allowDataInLabel=*/ false); } if (!EvalUtils.isNullOrNone(makeVariablesUnchecked)) { Map makeVariables = Type.STRING_DICT.convert(makeVariablesUnchecked, "make_variables", ruleLabel); command = expandMakeVariables(attribute, command, makeVariables); } List inputs = new ArrayList<>(); inputs.addAll(helper.getResolvedTools()); ImmutableMap executionRequirements = ImmutableMap.copyOf( SkylarkDict.castSkylarkDictOrNoneToDict( executionRequirementsUnchecked, String.class, String.class, "execution_requirements")); PathFragment shExecutable = ShToolchain.getPathOrError(ruleContext); List argv = helper.buildCommandLine( shExecutable, command, inputs, SCRIPT_SUFFIX, executionRequirements); return Tuple.of( MutableList.copyOf(env, inputs), MutableList.copyOf(env, argv), helper.getToolsRunfilesSuppliers()); } /** * Ensures the given {@link Map} has keys that have {@link Label} type and values that have either * {@link Iterable} or {@link SkylarkNestedSet} type, and raises {@link EvalException} otherwise. * Returns a corresponding map where any sets are replaced by iterables. */ // TODO(bazel-team): find a better way to typecheck this argument. @SuppressWarnings("unchecked") private static Map> checkLabelDict( Map labelDict, Location loc, Environment env) throws EvalException { Map> convertedMap = new HashMap<>(); for (Map.Entry entry : labelDict.entrySet()) { Object key = entry.getKey(); if (!(key instanceof Label)) { throw new EvalException(loc, Printer.format("invalid key %r in 'label_dict'", key)); } ImmutableList.Builder files = ImmutableList.builder(); Object val = entry.getValue(); Iterable valIter; try { valIter = EvalUtils.toIterableStrict(val, loc, env); } catch (EvalException ex) { // EvalException is thrown only if the type is wrong. throw new EvalException( loc, Printer.format("invalid value %r in 'label_dict': " + ex, val)); } for (Object file : valIter) { if (!(file instanceof Artifact)) { throw new EvalException(loc, Printer.format("invalid value %r in 'label_dict'", val)); } files.add((Artifact) file); } convertedMap.put((Label) key, files.build()); } return convertedMap; } /** suffix of script to be used in case the command is too long to fit on a single line */ private static final String SCRIPT_SUFFIX = ".script.sh"; private static void checkDeprecated( String newApi, String oldApi, Location loc, SkylarkSemantics semantics) throws EvalException { if (semantics.incompatibleNewActionsApi()) { throw new EvalException( loc, "Use " + newApi + " instead of " + oldApi + ". \n" + "Use --incompatible_new_actions_api=false to temporarily disable this check."); } } /** * Builds a map: Label -> List of files from the given labels * * @param knownLabels List of known labels * @return Immutable map with immutable collections as values */ private static ImmutableMap> makeLabelMap( Iterable knownLabels) { ImmutableMap.Builder> builder = ImmutableMap.builder(); for (TransitiveInfoCollection current : knownLabels) { builder.put( AliasProvider.getDependencyLabel(current), ImmutableList.copyOf(current.getProvider(FileProvider.class).getFilesToBuild())); } return builder.build(); } }