diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java new file mode 100644 index 0000000000..b82713f8e4 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java @@ -0,0 +1,423 @@ +// Copyright 2014 Google Inc. 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; + +import com.google.common.base.Preconditions; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.ExtraActionArtifactsProvider.ExtraArtifactSet; +import com.google.devtools.build.lib.analysis.LicensesProvider.TargetLicense; +import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.analysis.constraints.ConstraintSemantics; +import com.google.devtools.build.lib.analysis.constraints.EnvironmentCollection; +import com.google.devtools.build.lib.analysis.constraints.SupportedEnvironments; +import com.google.devtools.build.lib.analysis.constraints.SupportedEnvironmentsProvider; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.collect.nestedset.Order; +import com.google.devtools.build.lib.events.Location; +import com.google.devtools.build.lib.packages.License; +import com.google.devtools.build.lib.packages.Rule; +import com.google.devtools.build.lib.packages.TargetUtils; +import com.google.devtools.build.lib.packages.Type; +import com.google.devtools.build.lib.rules.extra.ExtraActionMapProvider; +import com.google.devtools.build.lib.rules.extra.ExtraActionSpec; +import com.google.devtools.build.lib.rules.test.ExecutionInfoProvider; +import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider; +import com.google.devtools.build.lib.rules.test.TestActionBuilder; +import com.google.devtools.build.lib.rules.test.TestProvider; +import com.google.devtools.build.lib.rules.test.TestProvider.TestParams; +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.Label; +import com.google.devtools.build.lib.syntax.SkylarkList; +import com.google.devtools.build.lib.syntax.SkylarkNestedSet; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Builder class for analyzed rule instances (i.e., instances of {@link ConfiguredTarget}). + */ +public final class RuleConfiguredTargetBuilder { + private final RuleContext ruleContext; + private final Map<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> providers = + new LinkedHashMap<>(); + private final ImmutableMap.Builder<String, Object> skylarkProviders = ImmutableMap.builder(); + + /** These are supported by all configured targets and need to be specially handled. */ + private NestedSet<Artifact> filesToBuild = NestedSetBuilder.emptySet(Order.STABLE_ORDER); + private RunfilesSupport runfilesSupport; + private Artifact executable; + private ImmutableList<Artifact> mandatoryStampFiles; + private ImmutableSet<Action> actionsWithoutExtraAction = ImmutableSet.of(); + + public RuleConfiguredTargetBuilder(RuleContext ruleContext) { + this.ruleContext = ruleContext; + add(LicensesProvider.class, initializeLicensesProvider()); + add(VisibilityProvider.class, new VisibilityProviderImpl(ruleContext.getVisibility())); + } + + /** + * Constructs the RuleConfiguredTarget instance based on the values set for this Builder. + */ + public ConfiguredTarget build() { + if (ruleContext.getConfiguration().enforceConstraints()) { + checkConstraints(); + } + if (ruleContext.hasErrors()) { + return null; + } + + FilesToRunProvider filesToRunProvider = new FilesToRunProvider(ruleContext.getLabel(), + RuleContext.getFilesToRun(runfilesSupport, filesToBuild), runfilesSupport, executable); + add(FileProvider.class, new FileProvider(ruleContext.getLabel(), filesToBuild)); + add(FilesToRunProvider.class, filesToRunProvider); + + // Create test action and artifacts if target was successfully initialized + // and is a test. + if (TargetUtils.isTestRule(ruleContext.getTarget())) { + Preconditions.checkState(runfilesSupport != null); + add(TestProvider.class, initializeTestProvider(filesToRunProvider)); + } + add(ExtraActionArtifactsProvider.class, initializeExtraActions()); + return new RuleConfiguredTarget( + ruleContext, mandatoryStampFiles, skylarkProviders.build(), providers); + } + + /** + * Invokes Blaze's constraint enforcement system: checks that this rule's dependencies + * support its environments and reports appropriate errors if violations are found. Also + * publishes this rule's supported environments for the rules that depend on it. + */ + private void checkConstraints() { + if (providers.get(SupportedEnvironmentsProvider.class) == null) { + // Note the "environment" rule sets its own SupportedEnvironmentProvider instance, so this + // logic is for "normal" rules that just want to apply default semantics. + EnvironmentCollection supportedEnvironments = + ConstraintSemantics.getSupportedEnvironments(ruleContext); + if (supportedEnvironments != null) { + add(SupportedEnvironmentsProvider.class, new SupportedEnvironments(supportedEnvironments)); + ConstraintSemantics.checkConstraints(ruleContext, supportedEnvironments); + } + } + } + + private TestProvider initializeTestProvider(FilesToRunProvider filesToRunProvider) { + int explicitShardCount = ruleContext.attributes().get("shard_count", Type.INTEGER); + if (explicitShardCount < 0 + && ruleContext.getRule().isAttributeValueExplicitlySpecified("shard_count")) { + ruleContext.attributeError("shard_count", "Must not be negative."); + } + if (explicitShardCount > 50) { + ruleContext.attributeError("shard_count", + "Having more than 50 shards is indicative of poor test organization. " + + "Please reduce the number of shards."); + } + final TestParams testParams = new TestActionBuilder(ruleContext) + .setFilesToRunProvider(filesToRunProvider) + .setInstrumentedFiles(findProvider(InstrumentedFilesProvider.class)) + .setExecutionRequirements(findProvider(ExecutionInfoProvider.class)) + .setShardCount(explicitShardCount) + .build(); + final ImmutableList<String> testTags = + ImmutableList.copyOf(ruleContext.getRule().getRuleTags()); + return new TestProvider(testParams, testTags); + } + + private LicensesProvider initializeLicensesProvider() { + if (!ruleContext.getConfiguration().checkLicenses()) { + return LicensesProviderImpl.EMPTY; + } + + NestedSetBuilder<TargetLicense> builder = NestedSetBuilder.linkOrder(); + BuildConfiguration configuration = ruleContext.getConfiguration(); + Rule rule = ruleContext.getRule(); + License toolOutputLicense = rule.getToolOutputLicense(ruleContext.attributes()); + if (configuration.isHostConfiguration() && toolOutputLicense != null) { + if (toolOutputLicense != License.NO_LICENSE) { + builder.add(new TargetLicense(rule.getLabel(), toolOutputLicense)); + } + } else { + if (rule.getLicense() != License.NO_LICENSE) { + builder.add(new TargetLicense(rule.getLabel(), rule.getLicense())); + } + + for (TransitiveInfoCollection dep : ruleContext.getConfiguredTargetMap().values()) { + LicensesProvider provider = dep.getProvider(LicensesProvider.class); + if (provider != null) { + builder.addTransitive(provider.getTransitiveLicenses()); + } + } + } + + return new LicensesProviderImpl(builder.build()); + } + + /** + * Scans {@code action_listeners} associated with this build to see if any + * {@code extra_actions} should be added to this configured target. If any + * action_listeners are present, a partial visit of the artifact/action graph + * is performed (for as long as actions found are owned by this {@link + * ConfiguredTarget}). Any actions that match the {@code action_listener} + * get an {@code extra_action} associated. The output artifacts of the + * extra_action are reported to the {@link AnalysisEnvironment} for + * bookkeeping. + */ + private ExtraActionArtifactsProvider initializeExtraActions() { + BuildConfiguration configuration = ruleContext.getConfiguration(); + if (configuration.isHostConfiguration()) { + return ExtraActionArtifactsProvider.EMPTY; + } + + ImmutableList<Artifact> extraActionArtifacts = ImmutableList.of(); + NestedSetBuilder<ExtraArtifactSet> builder = NestedSetBuilder.stableOrder(); + + List<Label> actionListenerLabels = configuration.getActionListeners(); + if (!actionListenerLabels.isEmpty() + && ruleContext.getRule().getAttributeDefinition(":action_listener") != null) { + ExtraActionsVisitor visitor = new ExtraActionsVisitor(ruleContext, + computeMnemonicsToExtraActionMap()); + + // The action list is modified within the body of the loop by the addExtraAction() call, + // thus the copy + for (Action action : ImmutableList.copyOf( + ruleContext.getAnalysisEnvironment().getRegisteredActions())) { + if (!actionsWithoutExtraAction.contains(action)) { + visitor.addExtraAction(action); + } + } + + extraActionArtifacts = visitor.getAndResetExtraArtifacts(); + if (!extraActionArtifacts.isEmpty()) { + builder.add(ExtraArtifactSet.of(ruleContext.getLabel(), extraActionArtifacts)); + } + } + + // Add extra action artifacts from dependencies + for (TransitiveInfoCollection dep : ruleContext.getConfiguredTargetMap().values()) { + ExtraActionArtifactsProvider provider = + dep.getProvider(ExtraActionArtifactsProvider.class); + if (provider != null) { + builder.addTransitive(provider.getTransitiveExtraActionArtifacts()); + } + } + + if (mandatoryStampFiles != null && !mandatoryStampFiles.isEmpty()) { + builder.add(ExtraArtifactSet.of(ruleContext.getLabel(), mandatoryStampFiles)); + } + + if (extraActionArtifacts.isEmpty() && builder.isEmpty()) { + return ExtraActionArtifactsProvider.EMPTY; + } + return new ExtraActionArtifactsProvider(extraActionArtifacts, builder.build()); + } + + /** + * Populates the configuration specific mnemonicToExtraActionMap + * based on all action_listers selected by the user (via the blaze option + * --experimental_action_listener=<target>). + */ + private Multimap<String, ExtraActionSpec> computeMnemonicsToExtraActionMap() { + // We copy the multimap here every time. This could be expensive. + Multimap<String, ExtraActionSpec> mnemonicToExtraActionMap = HashMultimap.create(); + for (TransitiveInfoCollection actionListener : + ruleContext.getPrerequisites(":action_listener", Mode.HOST)) { + ExtraActionMapProvider provider = actionListener.getProvider(ExtraActionMapProvider.class); + if (provider == null) { + ruleContext.ruleError(String.format( + "Unable to match experimental_action_listeners to this rule. " + + "Specified target %s is not an action_listener rule", + actionListener.getLabel().toString())); + } else { + mnemonicToExtraActionMap.putAll(provider.getExtraActionMap()); + } + } + return mnemonicToExtraActionMap; + } + + private <T extends TransitiveInfoProvider> T findProvider(Class<T> clazz) { + return clazz.cast(providers.get(clazz)); + } + + /** + * Add a specific provider with a given value. + */ + public <T extends TransitiveInfoProvider> RuleConfiguredTargetBuilder add(Class<T> key, T value) { + return addProvider(key, value); + } + + /** + * Add a specific provider with a given value. + */ + public RuleConfiguredTargetBuilder addProvider( + Class<? extends TransitiveInfoProvider> key, TransitiveInfoProvider value) { + Preconditions.checkNotNull(key); + Preconditions.checkNotNull(value); + AnalysisUtils.checkProvider(key); + providers.put(key, value); + return this; + } + + /** + * Add multiple providers with given values. + */ + public RuleConfiguredTargetBuilder addProviders( + Map<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> providers) { + for (Entry<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> provider : + providers.entrySet()) { + addProvider(provider.getKey(), provider.getValue()); + } + return this; + } + + /** + * Add a Skylark transitive info. The provider value must be safe (i.e. a String, a Boolean, + * an Integer, an Artifact, a Label, None, a Java TransitiveInfoProvider or something composed + * from these in Skylark using lists, sets, structs or dicts). Otherwise an EvalException is + * thrown. + */ + public RuleConfiguredTargetBuilder addSkylarkTransitiveInfo( + String name, Object value, Location loc) throws EvalException { + try { + checkSkylarkObjectSafe(value); + } catch (IllegalArgumentException e) { + throw new EvalException(loc, String.format("Value of provider '%s' is of an illegal type: %s", + name, e.getMessage())); + } + skylarkProviders.put(name, value); + return this; + } + + /** + * Add a Skylark transitive info. The provider value must be safe. + */ + public RuleConfiguredTargetBuilder addSkylarkTransitiveInfo( + String name, Object value) { + checkSkylarkObjectSafe(value); + skylarkProviders.put(name, value); + return this; + } + + /** + * Check if the value provided by a Skylark provider is safe (i.e. can be a + * TransitiveInfoProvider value). + */ + private void checkSkylarkObjectSafe(Object value) { + if (!isSimpleSkylarkObjectSafe(value.getClass()) + // Java transitive Info Providers are accessible from Skylark. + || value instanceof TransitiveInfoProvider) { + checkCompositeSkylarkObjectSafe(value); + } + } + + private void checkCompositeSkylarkObjectSafe(Object object) { + if (object instanceof SkylarkList) { + SkylarkList list = (SkylarkList) object; + if (list == SkylarkList.EMPTY_LIST || isSimpleSkylarkObjectSafe(list.getGenericType())) { + // Try not to iterate over the list if avoidable. + return; + } + // The list can be a tuple or a list of composite items. + for (Object listItem : list) { + checkSkylarkObjectSafe(listItem); + } + return; + } else if (object instanceof SkylarkNestedSet) { + // SkylarkNestedSets cannot have composite items. + Class<?> genericType = ((SkylarkNestedSet) object).getGenericType(); + if (!genericType.equals(Object.class) && !isSimpleSkylarkObjectSafe(genericType)) { + throw new IllegalArgumentException(EvalUtils.getDatatypeName(genericType)); + } + return; + } else if (object instanceof Map<?, ?>) { + for (Map.Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) { + checkSkylarkObjectSafe(entry.getKey()); + checkSkylarkObjectSafe(entry.getValue()); + } + return; + } else if (object instanceof ClassObject) { + ClassObject struct = (ClassObject) object; + for (String key : struct.getKeys()) { + checkSkylarkObjectSafe(struct.getValue(key)); + } + return; + } + throw new IllegalArgumentException(EvalUtils.getDatatypeName(object)); + } + + private boolean isSimpleSkylarkObjectSafe(Class<?> type) { + return type.equals(String.class) + || type.equals(Integer.class) + || type.equals(Boolean.class) + || Artifact.class.isAssignableFrom(type) + || type.equals(Label.class) + || type.equals(Environment.NoneType.class); + } + + /** + * Set the runfiles support for executable targets. + */ + public RuleConfiguredTargetBuilder setRunfilesSupport( + RunfilesSupport runfilesSupport, Artifact executable) { + this.runfilesSupport = runfilesSupport; + this.executable = executable; + return this; + } + + /** + * Set the files to build. + */ + public RuleConfiguredTargetBuilder setFilesToBuild(NestedSet<Artifact> filesToBuild) { + this.filesToBuild = filesToBuild; + return this; + } + + /** + * Set the baseline coverage Artifacts. + */ + public RuleConfiguredTargetBuilder setBaselineCoverageArtifacts( + Collection<Artifact> artifacts) { + return add(BaselineCoverageArtifactsProvider.class, + new BaselineCoverageArtifactsProvider(ImmutableList.copyOf(artifacts))); + } + + /** + * Set the mandatory stamp files. + */ + public RuleConfiguredTargetBuilder setMandatoryStampFiles(ImmutableList<Artifact> files) { + this.mandatoryStampFiles = files; + return this; + } + + /** + * Set the extra action pseudo actions. + */ + public RuleConfiguredTargetBuilder setActionsWithoutExtraAction( + ImmutableSet<Action> actions) { + this.actionsWithoutExtraAction = actions; + return this; + } +} |