| // 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.starlark; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException; |
| import com.google.devtools.build.lib.analysis.ActionsProvider; |
| import com.google.devtools.build.lib.analysis.CachingAnalysisEnvironment; |
| import com.google.devtools.build.lib.analysis.ConfiguredTarget; |
| import com.google.devtools.build.lib.analysis.DefaultInfo; |
| import com.google.devtools.build.lib.analysis.RequiredConfigFragmentsProvider; |
| import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; |
| import com.google.devtools.build.lib.analysis.RuleContext; |
| import com.google.devtools.build.lib.analysis.RunEnvironmentInfo; |
| import com.google.devtools.build.lib.analysis.Runfiles; |
| import com.google.devtools.build.lib.analysis.RunfilesProvider; |
| import com.google.devtools.build.lib.analysis.RunfilesSupport; |
| import com.google.devtools.build.lib.analysis.StarlarkProviderValidationUtil; |
| import com.google.devtools.build.lib.analysis.test.CoverageCommon; |
| import com.google.devtools.build.lib.analysis.test.InstrumentedFilesInfo; |
| import com.google.devtools.build.lib.collect.nestedset.Depset; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| import com.google.devtools.build.lib.packages.AdvertisedProviderSet; |
| import com.google.devtools.build.lib.packages.BuiltinProvider; |
| import com.google.devtools.build.lib.packages.Info; |
| import com.google.devtools.build.lib.packages.Provider; |
| import com.google.devtools.build.lib.packages.RuleClass; |
| import com.google.devtools.build.lib.packages.StarlarkInfo; |
| import com.google.devtools.build.lib.packages.StarlarkProviderIdentifier; |
| import com.google.devtools.build.lib.packages.StructImpl; |
| import com.google.devtools.build.lib.packages.StructProvider; |
| import com.google.devtools.build.lib.packages.TargetUtils; |
| import com.google.devtools.build.lib.packages.Type; |
| import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import com.google.errorprone.annotations.FormatMethod; |
| import java.util.Collections; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import javax.annotation.Nullable; |
| import net.starlark.java.eval.Dict; |
| import net.starlark.java.eval.EvalException; |
| import net.starlark.java.eval.Sequence; |
| import net.starlark.java.eval.Starlark; |
| import net.starlark.java.eval.StarlarkValue; |
| import net.starlark.java.syntax.Location; |
| |
| /** |
| * A helper class to build Rule Configured Targets via runtime loaded rule implementations defined |
| * using the Starlark Build Extension Language. |
| */ |
| public final class StarlarkRuleConfiguredTargetUtil { |
| |
| private StarlarkRuleConfiguredTargetUtil() {} |
| |
| private static final ImmutableSet<String> DEFAULT_PROVIDER_FIELDS = |
| ImmutableSet.of("files", "runfiles", "data_runfiles", "default_runfiles", "executable"); |
| |
| /** |
| * Evaluates the rule's implementation function and returns what it returns (raw providers). |
| * |
| * <p>If there were errors during the evaluation or the type of the returned object is obviously |
| * wrong, it sets ruleErrors on the ruleContext and returns null. |
| * |
| * <p>Unchecked exception {@code UncheckedEvalException}s and {@code MissingDepException} may be |
| * thrown. |
| */ |
| // TODO(blaze-team): Legacy providers are preventing to change the return type to Sequence<Info>. |
| @Nullable |
| public static Object evalRule(RuleContext ruleContext) throws InterruptedException { |
| RuleClass ruleClass = ruleContext.getRule().getRuleClassObject(); |
| if (ruleClass.getRuleClassType().equals(RuleClass.Builder.RuleClassType.WORKSPACE)) { |
| ruleContext.ruleError( |
| "Found reference to a workspace rule in a context where a build" |
| + " rule was expected; probably a reference to a target in that external" |
| + " repository, properly specified as @reponame//path/to/package:target," |
| + " should have been specified by the requesting rule."); |
| return null; |
| } |
| |
| // TODO(blaze-team): expect_failure attribute is special for all rule classes, but it should |
| // be special only for analysis tests |
| String expectFailure = ruleContext.attributes().get("expect_failure", Type.STRING); |
| Object providersRaw; |
| try { |
| // call rule.implementation(ctx) |
| providersRaw = |
| Starlark.fastcall( |
| ruleContext.getStarlarkThread(), |
| ruleClass.getConfiguredTargetFunction(), |
| /* positional= */ new Object[] {ruleContext.getStarlarkRuleContext()}, |
| /* named= */ new Object[0]); |
| |
| } catch (Starlark.UncheckedEvalException ex) { |
| // MissingDepException is expected to transit through Starlark execution. |
| throw ex.getCause() instanceof CachingAnalysisEnvironment.MissingDepException |
| ? (CachingAnalysisEnvironment.MissingDepException) ex.getCause() |
| : ex; |
| |
| } catch (EvalException ex) { |
| // An error occurred during the rule.implementation call |
| |
| // If the error was expected by an analysis test, return None, to produce an empty target. |
| if (!expectFailure.isEmpty() && ex.getMessage().matches(expectFailure)) { |
| return Starlark.NONE; |
| } |
| |
| // Emit a single event that spans multiple lines: |
| // ERROR p/BUILD:1:1: in foo_library rule //p:p: |
| // Traceback: |
| // File foo.bzl, line 1, in foo_library_impl: |
| // ... |
| ruleContext.ruleError("\n" + ex.getMessageWithStack()); |
| return null; |
| } |
| |
| // Errors already reported? |
| if (ruleContext.hasErrors()) { |
| return null; |
| } |
| |
| // Wrong result type? |
| if (!(providersRaw instanceof Info |
| || providersRaw == Starlark.NONE |
| || providersRaw instanceof Iterable)) { |
| ruleContext.ruleError( |
| String.format( |
| "Rule should return a struct or a list, but got %s", Starlark.type(providersRaw))); |
| return null; |
| } |
| |
| // Did the Starlark implementation function fail to fail as expected? |
| if (!expectFailure.isEmpty()) { |
| ruleContext.ruleError("Expected failure not found: " + expectFailure); |
| return null; |
| } |
| |
| return providersRaw; |
| } |
| |
| private static void checkDeclaredProviders( |
| ConfiguredTarget configuredTarget, AdvertisedProviderSet advertisedProviders) |
| throws EvalException { |
| for (StarlarkProviderIdentifier providerId : advertisedProviders.getStarlarkProviders()) { |
| if (configuredTarget.get(providerId) == null) { |
| throw Starlark.errorf( |
| "rule advertised the '%s' provider, but this provider was not among those returned", |
| providerId); |
| } |
| } |
| } |
| |
| /** |
| * Creates a Rule Configured Target from the raw providers returned by the rule's implementation |
| * function. |
| * |
| * <p>If there are problems with the raw providers, it sets ruleErrors on the ruleContext and |
| * returns null. |
| */ |
| @Nullable |
| public static ConfiguredTarget createTarget( |
| RuleContext context, |
| Object rawProviders, |
| AdvertisedProviderSet advertisedProviders, |
| boolean isDefaultExecutableCreated, |
| @Nullable RequiredConfigFragmentsProvider requiredConfigFragmentsProvider) |
| throws InterruptedException, ActionConflictException { |
| RuleConfiguredTargetBuilder builder = new RuleConfiguredTargetBuilder(context); |
| |
| // Location of rule.implementation function. |
| Location implLoc = |
| context.getRule().getRuleClassObject().getConfiguredTargetFunction().getLocation(); |
| |
| // TODO(adonovan): clean up addProviders' error handling, |
| // reporting provider validity errors through ruleError |
| // where possible. This allows for multiple events, with independent |
| // locations, even for the same root cause. |
| // The required change is fiddly due to frequent and nested use of |
| // Structure.getField, Sequence.cast, and similar operators. |
| try { |
| addProviders(context, builder, rawProviders, implLoc, isDefaultExecutableCreated); |
| } catch (EvalException ex) { |
| // Emit a single event that spans two lines (see infoError). |
| // The message typically starts with another location, e.g. of provider creation. |
| // ERROR p/BUILD:1:1: in foo_library rule //p:p: |
| // ...message... |
| context.ruleError("\n" + ex.getMessage()); |
| return null; |
| } |
| |
| // This provider is kept out of `addProviders` method, because it's not generated by the |
| // Starlark rule and because `addProviders` will be simplified by the legacy providers removal |
| // RequiredConfigFragmentsProvider may be removed with removal of Android feature flags. |
| if (requiredConfigFragmentsProvider != null) { |
| builder.addProvider(requiredConfigFragmentsProvider); |
| } |
| |
| ConfiguredTarget ct; |
| try { |
| // This also throws InterruptedException from a convoluted dependency: |
| // TestActionBuilder -> TestTargetExecutionSettings -> CommandLine -> Starlark. |
| ct = builder.build(); // may be null |
| } catch (IllegalArgumentException ex) { |
| // TODO(adonovan): eliminate this abuse of unchecked exceptions. |
| // Emit a single event that spans two lines (see infoError). |
| // The message typically starts with another location, e.g. of provider creation. |
| // ERROR p/BUILD:1:1: in foo_library rule //p:p: |
| // ...message... |
| context.ruleError("\n" + implLoc + ": " + ex.getMessage()); |
| return null; |
| } |
| |
| if (ct != null) { |
| // If there was error creating the ConfiguredTarget, no further validation is needed. |
| // Null will be returned and the errors thus reported. |
| try { |
| // Check all artifacts have actions. Despite signature, must be done after build(). |
| StarlarkProviderValidationUtil.validateArtifacts(context); |
| // Check all advertised providers were created. |
| checkDeclaredProviders(ct, advertisedProviders); |
| } catch (EvalException ex) { |
| context.ruleError("\n" + implLoc + ": " + ex.getMessage()); |
| return null; |
| } |
| } |
| |
| return ct; |
| } |
| |
| private static void addOutputGroups(Object outputGroups, RuleConfiguredTargetBuilder builder) |
| throws EvalException { |
| for (Map.Entry<String, StarlarkValue> entry : |
| Dict.cast(outputGroups, String.class, StarlarkValue.class, "output_groups").entrySet()) { |
| String outputGroup = entry.getKey(); |
| NestedSet<Artifact> artifacts = convertToOutputGroupValue(outputGroup, entry.getValue()); |
| builder.addOutputGroup(outputGroup, artifacts); |
| } |
| } |
| |
| private static void addInstrumentedFiles( |
| StructImpl insStruct, RuleContext ruleContext, RuleConfiguredTargetBuilder builder) |
| throws EvalException { |
| List<String> extensions = null; |
| if (insStruct.getFieldNames().contains("extensions")) { |
| extensions = Sequence.cast(insStruct.getValue("extensions"), String.class, "extensions"); |
| } |
| |
| List<String> dependencyAttributes = Collections.emptyList(); |
| if (insStruct.getFieldNames().contains("dependency_attributes")) { |
| dependencyAttributes = |
| Sequence.cast( |
| insStruct.getValue("dependency_attributes"), String.class, "dependency_attributes"); |
| } |
| |
| List<String> sourceAttributes = Collections.emptyList(); |
| if (insStruct.getFieldNames().contains("source_attributes")) { |
| sourceAttributes = |
| Sequence.cast(insStruct.getValue("source_attributes"), String.class, "source_attributes"); |
| } |
| |
| InstrumentedFilesInfo instrumentedFilesProvider = |
| CoverageCommon.createInstrumentedFilesInfo( |
| ruleContext, sourceAttributes, dependencyAttributes, extensions); |
| builder.addNativeDeclaredProvider(instrumentedFilesProvider); |
| } |
| |
| public static NestedSet<Artifact> convertToOutputGroupValue(String outputGroup, Object objects) |
| throws EvalException { |
| // regrettable preemptive allocation of error message |
| String what = "output group '" + outputGroup + "'"; |
| return objects instanceof Sequence |
| ? NestedSetBuilder.<Artifact>stableOrder() |
| .addAll(Sequence.cast(objects, Artifact.class, what)) |
| .build() |
| : Depset.cast(objects, Artifact.class, what); |
| } |
| |
| private static void addProviders( |
| RuleContext context, |
| RuleConfiguredTargetBuilder builder, |
| Object rawProviders, |
| Location implLoc, |
| boolean isDefaultExecutableCreated) |
| throws EvalException, InterruptedException { |
| |
| StructImpl oldStyleProviders = |
| StarlarkInfo.create(StructProvider.STRUCT, ImmutableMap.of(), implLoc); |
| Map<Provider.Key, Info> declaredProviders = new LinkedHashMap<>(); |
| |
| if (rawProviders instanceof Info) { |
| // Either an old-style struct or a single declared provider (not in a list) |
| Info info = (Info) rawProviders; |
| if (getProviderKey(info).equals(StructProvider.STRUCT.getKey())) { |
| if (context |
| .getAnalysisEnvironment() |
| .getStarlarkSemantics() |
| .getBool(BuildLanguageOptions.INCOMPATIBLE_DISALLOW_STRUCT_PROVIDER_SYNTAX)) { |
| throw infoError( |
| info, |
| "Returning a struct from a rule implementation function is deprecated and will " |
| + "be removed soon. It may be temporarily re-enabled by setting " |
| + "--incompatible_disallow_struct_provider_syntax=false . See " |
| + "https://github.com/bazelbuild/bazel/issues/7347 for details."); |
| } |
| |
| // Old-style struct, but it may contain declared providers |
| StructImpl struct = (StructImpl) rawProviders; |
| oldStyleProviders = struct; |
| |
| Object providersField = struct.getValue("providers"); |
| if (providersField != null) { |
| for (Info provider : Sequence.cast(providersField, Info.class, "providers")) { |
| Provider.Key providerKey = getProviderKey(provider); |
| if (declaredProviders.put(providerKey, provider) != null) { |
| context.ruleError("Multiple conflicting returned providers with key " + providerKey); |
| } |
| } |
| } |
| } else { |
| if (info instanceof StarlarkInfo) { |
| info = ((StarlarkInfo) info).unsafeOptimizeMemoryLayout(); |
| } |
| Provider.Key providerKey = getProviderKey(info); |
| // Single declared provider |
| declaredProviders.put(providerKey, info); |
| } |
| } else if (rawProviders instanceof Sequence) { |
| // Sequence of declared providers |
| for (Info provider : |
| Sequence.cast(rawProviders, Info.class, "result of rule implementation function")) { |
| if (provider instanceof StarlarkInfo) { |
| // Provider instances are optimised recursively, without optimising elements of the list. |
| // Tradeoff is that some object may be duplicated if they are reachable by more than one |
| // path, but we don't expect that much in practice. |
| provider = ((StarlarkInfo) provider).unsafeOptimizeMemoryLayout(); |
| } |
| Provider.Key providerKey = getProviderKey(provider); |
| if (declaredProviders.put(providerKey, provider) != null) { |
| context.ruleError("Multiple conflicting returned providers with key " + providerKey); |
| } |
| } |
| } |
| |
| boolean defaultProviderProvidedExplicitly = false; |
| |
| for (Info declaredProvider : declaredProviders.values()) { |
| if (getProviderKey(declaredProvider).equals(DefaultInfo.PROVIDER.getKey())) { |
| parseDefaultProviderFields( |
| (DefaultInfo) declaredProvider, context, builder, isDefaultExecutableCreated); |
| defaultProviderProvidedExplicitly = true; |
| } else if (getProviderKey(declaredProvider).equals(RunEnvironmentInfo.PROVIDER.getKey()) |
| && !(context.getRule().getRuleClassObject().isExecutableStarlark() |
| || context.isTestTarget())) { |
| String message = |
| "Returning RunEnvironmentInfo from a non-executable, non-test target has no effect"; |
| RunEnvironmentInfo runEnvironmentInfo = (RunEnvironmentInfo) declaredProvider; |
| if (runEnvironmentInfo.shouldErrorOnNonExecutableRule()) { |
| context.ruleError(message); |
| } else { |
| context.ruleWarning(message); |
| builder.addStarlarkDeclaredProvider(declaredProvider); |
| } |
| } else { |
| builder.addStarlarkDeclaredProvider(declaredProvider); |
| } |
| } |
| |
| if (!defaultProviderProvidedExplicitly) { |
| parseDefaultProviderFields(oldStyleProviders, context, builder, isDefaultExecutableCreated); |
| } |
| |
| for (String field : oldStyleProviders.getFieldNames()) { |
| if (DEFAULT_PROVIDER_FIELDS.contains(field)) { |
| // These fields have already been parsed above. |
| // If a default provider has been provided explicitly then it's an error that they also |
| // occur here. |
| if (defaultProviderProvidedExplicitly) { |
| throw infoError( |
| oldStyleProviders, |
| "Provider '%s' should be specified in DefaultInfo if it's provided explicitly.", |
| field); |
| } |
| } else if (field.equals("output_groups")) { |
| addOutputGroups(oldStyleProviders.getValue(field), builder); |
| } else if (field.equals("instrumented_files")) { |
| addInstrumentedFiles( |
| oldStyleProviders.getValue("instrumented_files", StructImpl.class), context, builder); |
| } else if (!field.equals("providers")) { // "providers" already handled above. |
| addProviderFromLegacySyntax( |
| builder, oldStyleProviders, field, oldStyleProviders.getValue(field)); |
| } |
| } |
| } |
| |
| // Returns an EvalException whose message has the info's creation location as a prefix. |
| // The exception is intended to be reported as ruleErrors by createTarget. |
| @FormatMethod |
| private static EvalException infoError(Info info, String format, Object... args) { |
| return Starlark.errorf("%s: %s", info.getCreationLocation(), String.format(format, args)); |
| } |
| |
| @SuppressWarnings("deprecation") // For legacy migrations |
| private static void addProviderFromLegacySyntax( |
| RuleConfiguredTargetBuilder builder, |
| StructImpl oldStyleProviders, |
| String fieldName, |
| Object value) |
| throws EvalException { |
| builder.addStarlarkTransitiveInfo(fieldName, value); |
| |
| if (value instanceof Info) { |
| Info info = (Info) value; |
| |
| // To facilitate migration off legacy provider syntax, implicitly set the modern provider key |
| // and the canonical legacy provider key if applicable. |
| if (shouldAddWithModernKey(builder, oldStyleProviders, fieldName, info)) { |
| builder.addNativeDeclaredProvider(info); |
| } |
| |
| if (info.getProvider() instanceof BuiltinProvider.WithLegacyStarlarkName) { |
| BuiltinProvider.WithLegacyStarlarkName providerWithLegacyName = |
| (BuiltinProvider.WithLegacyStarlarkName) info.getProvider(); |
| if (shouldAddWithLegacyKey(oldStyleProviders, providerWithLegacyName)) { |
| builder.addStarlarkTransitiveInfo(providerWithLegacyName.getStarlarkName(), info); |
| } |
| } |
| } |
| } |
| |
| @SuppressWarnings("deprecation") // For legacy migrations |
| private static boolean shouldAddWithModernKey( |
| RuleConfiguredTargetBuilder builder, |
| StructImpl oldStyleProviders, |
| String fieldName, |
| Info info) |
| throws EvalException { |
| // If the modern key is already set, do nothing. |
| if (builder.containsProviderKey(info.getProvider().getKey())) { |
| return false; |
| } |
| if (info.getProvider() instanceof BuiltinProvider.WithLegacyStarlarkName) { |
| String canonicalLegacyKey = |
| ((BuiltinProvider.WithLegacyStarlarkName) info.getProvider()).getStarlarkName(); |
| // Add info using its modern key if it was specified using its canonical legacy key, or |
| // if no provider was used using that canonical legacy key. |
| return fieldName.equals(canonicalLegacyKey) |
| || oldStyleProviders.getValue(canonicalLegacyKey) == null; |
| } else { |
| return true; |
| } |
| } |
| |
| @SuppressWarnings("deprecation") // For legacy migrations |
| private static boolean shouldAddWithLegacyKey( |
| StructImpl oldStyleProviders, BuiltinProvider.WithLegacyStarlarkName provider) |
| throws EvalException { |
| String canonicalLegacyKey = provider.getStarlarkName(); |
| // Add info using its canonical legacy key if no provider was specified using that canonical |
| // legacy key. |
| return oldStyleProviders.getValue(canonicalLegacyKey) == null; |
| } |
| |
| /** |
| * Returns the provider key from an info (provider instance). |
| * |
| * @throws EvalException if the provider for this info object has not been exported, which can |
| * occur if the provider was declared in a non-global scope (for example a rule implementation |
| * function) |
| */ |
| private static Provider.Key getProviderKey(Info info) throws EvalException { |
| Provider provider = info.getProvider(); |
| if (!provider.isExported()) { |
| // TODO(adonovan): report separate error events at distinct locations: |
| // "cannot return non-exported provider" (at location of instantiation), and |
| // "provider definition not at top level" (at location of definition). |
| throw infoError( |
| info, |
| "The rule implementation function returned an instance of an unnamed provider. " |
| + "A provider becomes named by being assigned to a global variable in a .bzl file. " |
| + "(Provider defined at %s.)", |
| provider.getLocation()); |
| } |
| return provider.getKey(); |
| } |
| |
| /** |
| * Parses fields of (not necessarily a default) provider. If it is an actual default provider, |
| * throws an {@link EvalException} if there are unknown fields. |
| */ |
| // TODO(brandjon): Consider refactoring this method by splitting it into two versions for the |
| // case of DefaultInfo and the case of a legacy struct. They'd differ in how they parse the |
| // fields, then dispatch to a common validation helper and finally the common |
| // addSimpleProviders call. Also rename this method to make clear that it mutates the builder. |
| // If lagacy struct providers are removed first, this is moot. |
| private static void parseDefaultProviderFields( |
| StructImpl info, |
| RuleContext context, |
| RuleConfiguredTargetBuilder builder, |
| boolean isDefaultExecutableCreated) |
| throws EvalException, InterruptedException { |
| Depset files = null; |
| Runfiles statelessRunfiles = null; |
| Runfiles dataRunfiles = null; |
| Runfiles defaultRunfiles = null; |
| Artifact executable = null; |
| |
| if (getProviderKey(info).equals(DefaultInfo.PROVIDER.getKey())) { |
| DefaultInfo defaultInfo = (DefaultInfo) info; |
| |
| files = defaultInfo.getFiles(); |
| statelessRunfiles = defaultInfo.getStatelessRunfiles(); |
| dataRunfiles = defaultInfo.getDataRunfiles(); |
| defaultRunfiles = defaultInfo.getDefaultRunfiles(); |
| executable = defaultInfo.getExecutable(); |
| |
| } else { |
| // Rule implementations aren't required to return default-info fields via a DefaultInfo |
| // provider. They can return them as fields on the returned struct. For example, |
| // 'return struct(executable = foo)' instead of 'return DefaultInfo(executable = foo)'. |
| // TODO(cparsons): Look into deprecating this option. |
| for (String field : info.getFieldNames()) { |
| if (field.equals("files")) { |
| Object x = info.getValue("files"); |
| Depset.cast(x, Artifact.class, "files"); // may throw exception |
| files = (Depset) x; |
| } else if (field.equals("runfiles")) { |
| statelessRunfiles = info.getValue("runfiles", Runfiles.class); |
| } else if (field.equals("data_runfiles")) { |
| dataRunfiles = info.getValue("data_runfiles", Runfiles.class); |
| } else if (field.equals("default_runfiles")) { |
| defaultRunfiles = info.getValue("default_runfiles", Runfiles.class); |
| } else if (field.equals("executable") && info.getValue("executable") != null) { |
| executable = info.getValue("executable", Artifact.class); |
| } |
| } |
| |
| if ((statelessRunfiles != null) && (dataRunfiles != null || defaultRunfiles != null)) { |
| throw infoError( |
| info, |
| "Cannot specify the provider 'runfiles' together with 'data_runfiles' or" |
| + " 'default_runfiles'"); |
| } |
| } |
| |
| if (executable != null && !executable.getArtifactOwner().equals(context.getOwner())) { |
| throw infoError( |
| info, |
| "'executable' provided by an executable rule '%s' should be created " |
| + "by the same rule.", |
| context.getRule().getRuleClass()); |
| } |
| |
| boolean isExecutable = context.getRule().getRuleClassObject().isExecutableStarlark(); |
| if (executable != null && isExecutable && isDefaultExecutableCreated) { |
| Artifact defaultExecutable = context.createOutputArtifact(); |
| if (!executable.equals(defaultExecutable)) { |
| throw infoError( |
| info, |
| "The rule '%s' both accesses 'ctx.outputs.executable' and provides " |
| + "a different executable '%s'. Do not use 'ctx.output.executable'.", |
| context.getRule().getRuleClass(), |
| executable.getRootRelativePathString()); |
| } |
| } |
| |
| if (context.getRule().isAnalysisTest()) { |
| // The Starlark Build API should already throw exception if the rule implementation attempts |
| // to register any actions. This is just a check of this invariant. |
| Preconditions.checkState( |
| context.getAnalysisEnvironment().getRegisteredActions().isEmpty(), |
| "%s", |
| context.getLabel()); |
| |
| executable = context.createOutputArtifactScript(); |
| } |
| |
| if (executable == null && isExecutable) { |
| if (isDefaultExecutableCreated) { |
| // This doesn't actually create a new Artifact just returns the one |
| // created in StarlarkRuleContext. |
| executable = context.createOutputArtifact(); |
| } else { |
| throw infoError( |
| info, |
| "The rule '%s' is executable. " |
| + "It needs to create an executable File and pass it as the 'executable' " |
| + "parameter to the DefaultInfo it returns.", |
| context.getRule().getRuleClass()); |
| } |
| } |
| |
| addSimpleProviders( |
| builder, context, executable, files, statelessRunfiles, dataRunfiles, defaultRunfiles); |
| } |
| |
| private static void addSimpleProviders( |
| RuleConfiguredTargetBuilder builder, |
| RuleContext ruleContext, |
| Artifact executable, |
| @Nullable Depset files, |
| Runfiles statelessRunfiles, |
| Runfiles dataRunfiles, |
| Runfiles defaultRunfiles) |
| throws EvalException, InterruptedException { |
| |
| // TODO(bazel-team) if both 'files' and 'executable' are provided, 'files' overrides |
| // 'executable' |
| NestedSetBuilder<Artifact> filesToBuild = |
| NestedSetBuilder.<Artifact>stableOrder().addAll(ruleContext.getOutputArtifacts()); |
| if (executable != null) { |
| filesToBuild.add(executable); |
| } |
| builder.setFilesToBuild(filesToBuild.build()); |
| |
| if (files != null) { |
| // If we specify files_to_build we don't have the executable in it by default. |
| builder.setFilesToBuild(Depset.cast(files, Artifact.class, "files")); |
| } |
| |
| if (statelessRunfiles == null && dataRunfiles == null && defaultRunfiles == null) { |
| // No runfiles specified, set default |
| statelessRunfiles = Runfiles.EMPTY; |
| } |
| |
| RunfilesProvider runfilesProvider = |
| statelessRunfiles != null |
| ? RunfilesProvider.simple(mergeFiles(statelessRunfiles, executable, ruleContext)) |
| : RunfilesProvider.withData( |
| // The executable doesn't get into the default runfiles if we have runfiles states. |
| // This is to keep Starlark genrule consistent with the original genrule. |
| defaultRunfiles != null ? defaultRunfiles : Runfiles.EMPTY, |
| dataRunfiles != null ? dataRunfiles : Runfiles.EMPTY); |
| builder.addProvider(RunfilesProvider.class, runfilesProvider); |
| |
| Runfiles computedDefaultRunfiles = runfilesProvider.getDefaultRunfiles(); |
| // This works because we only allowed to call a rule *_test iff it's a test type rule. |
| boolean testRule = TargetUtils.isTestRuleName(ruleContext.getRule().getRuleClass()); |
| if (testRule && computedDefaultRunfiles.isEmpty()) { |
| throw Starlark.errorf("Test rules have to define runfiles"); |
| } |
| if (executable != null || testRule) { |
| RunfilesSupport runfilesSupport = null; |
| if (!computedDefaultRunfiles.isEmpty()) { |
| Preconditions.checkNotNull(executable, "executable must not be null"); |
| runfilesSupport = |
| RunfilesSupport.withExecutable(ruleContext, computedDefaultRunfiles, executable); |
| assertExecutableSymlinkPresent(runfilesSupport.getRunfiles(), executable); |
| } |
| builder.setRunfilesSupport(runfilesSupport, executable); |
| } |
| |
| if (ruleContext.getRule().getRuleClassObject().isStarlarkTestable()) { |
| Info actions = |
| ActionsProvider.create(ruleContext.getAnalysisEnvironment().getRegisteredActions()); |
| builder.addStarlarkDeclaredProvider(actions); |
| } |
| } |
| |
| private static void assertExecutableSymlinkPresent(Runfiles runfiles, Artifact executable) |
| throws EvalException { |
| // Extracting the map from Runfiles flattens a depset. |
| // TODO(cparsons): Investigate: Avoiding this flattening may be an efficiency win. |
| Map<PathFragment, Artifact> symlinks = runfiles.asMapWithoutRootSymlinks(); |
| if (!symlinks.containsValue(executable)) { |
| throw Starlark.errorf("main program %s not included in runfiles", executable); |
| } |
| } |
| |
| private static Runfiles mergeFiles( |
| Runfiles runfiles, Artifact executable, RuleContext ruleContext) { |
| if (executable == null) { |
| return runfiles; |
| } |
| return new Runfiles.Builder( |
| ruleContext.getWorkspaceName(), ruleContext.getConfiguration().legacyExternalRunfiles()) |
| .addArtifact(executable) |
| .merge(runfiles) |
| .build(); |
| } |
| } |