Update from Google.
--
MOE_MIGRATED_REVID=85702957
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AbstractConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/AbstractConfiguredTarget.java
new file mode 100644
index 0000000..e574978
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AbstractConfiguredTarget.java
@@ -0,0 +1,111 @@
+// 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.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+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.packages.PackageSpecification;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.syntax.ClassObject;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
+
+/**
+ * An abstract implementation of ConfiguredTarget in which all properties are
+ * assigned trivial default values.
+ */
+public abstract class AbstractConfiguredTarget
+ implements ConfiguredTarget, VisibilityProvider, ClassObject {
+ private final Target target;
+ private final BuildConfiguration configuration;
+
+ private final NestedSet<PackageSpecification> visibility;
+
+ AbstractConfiguredTarget(Target target,
+ BuildConfiguration configuration) {
+ this.target = target;
+ this.configuration = configuration;
+ this.visibility = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+ }
+
+ AbstractConfiguredTarget(TargetContext targetContext) {
+ this.target = targetContext.getTarget();
+ this.configuration = targetContext.getConfiguration();
+ this.visibility = targetContext.getVisibility();
+ }
+
+ @Override
+ public final NestedSet<PackageSpecification> getVisibility() {
+ return visibility;
+ }
+
+ @Override
+ public Target getTarget() {
+ return target;
+ }
+
+ @Override
+ public BuildConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ @Override
+ public Label getLabel() {
+ return getTarget().getLabel();
+ }
+
+ @Override
+ public String toString() {
+ return "ConfiguredTarget(" + getTarget().getLabel() + ", " + getConfiguration() + ")";
+ }
+
+ @Override
+ public <P extends TransitiveInfoProvider> P getProvider(Class<P> provider) {
+ AnalysisUtils.checkProvider(provider);
+ if (provider.isAssignableFrom(getClass())) {
+ return provider.cast(this);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public Object getValue(String name) {
+ if (name.equals("label")) {
+ return getLabel();
+ } else if (name.equals("files")) {
+ // A shortcut for files to build in Skylark. FileConfiguredTarget and RunleConfiguredTarget
+ // always has FileProvider and Error- and PackageGroupConfiguredTarget-s shouldn't be
+ // accessible in Skylark.
+ return SkylarkNestedSet.of(Artifact.class, getProvider(FileProvider.class).getFilesToBuild());
+ }
+ return get(name);
+ }
+
+ @Override
+ public String errorMessage(String name) {
+ return null;
+ }
+
+ @Override
+ public ImmutableCollection<String> getKeys() {
+ return ImmutableList.<String>builder().add("label").add("files").build();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AlwaysBuiltArtifactsProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/AlwaysBuiltArtifactsProvider.java
new file mode 100644
index 0000000..e4d40fc
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AlwaysBuiltArtifactsProvider.java
@@ -0,0 +1,46 @@
+// 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+/**
+ * Artifacts that should be built when a target is mentioned in the command line, but are neither in
+ * the {@code filesToBuild} nor in the runfiles.
+ *
+ * <p>
+ * Link actions, may not run a link for their transitive dependencies, so it does not force the
+ * source files in the transitive closure to be built by default. However, users expect builds to
+ * fail when there is an error in a dependent library, so we use this mechanism to force their
+ * compilation.
+ */
+@Immutable
+public final class AlwaysBuiltArtifactsProvider implements TransitiveInfoProvider {
+
+ private final NestedSet<Artifact> artifactsToAlwaysBuild;
+
+ public AlwaysBuiltArtifactsProvider(NestedSet<Artifact> artifactsToAlwaysBuild) {
+ this.artifactsToAlwaysBuild = artifactsToAlwaysBuild;
+ }
+
+ /**
+ * Returns the collection of artifacts to be built.
+ */
+ public NestedSet<Artifact> getArtifactsToAlwaysBuild() {
+ return artifactsToAlwaysBuild;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java
new file mode 100644
index 0000000..0bccc72
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java
@@ -0,0 +1,128 @@
+// 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.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionRegistry;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.MiddlemanFactory;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory.BuildInfoKey;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.skyframe.SkyFunction;
+
+/**
+ * The set of services that are provided to {@link ConfiguredTarget} objects
+ * during initialization.
+ */
+public interface AnalysisEnvironment extends ActionRegistry {
+ /**
+ * Returns a callback to be used in this build for reporting analysis errors.
+ */
+ EventHandler getEventHandler();
+
+ /**
+ * Returns whether any errors were reported to this instance.
+ */
+ boolean hasErrors();
+
+ /**
+ * Returns the artifact for the derived file {@code rootRelativePath}.
+ *
+ * <p>Creates the artifact if necessary and sets the root of that artifact to {@code root}.
+ */
+ Artifact getDerivedArtifact(PathFragment rootRelativePath, Root root);
+
+ /**
+ * Returns an artifact for the derived file {@code rootRelativePath} whose changes do not cause
+ * a rebuild.
+ *
+ * <p>Creates the artifact if necessary and sets the root of that artifact to {@code root}.
+ *
+ * <p>This is useful for files that store data that changes very frequently (e.g. current time)
+ * but does not substantially affect the result of the build.
+ */
+ Artifact getConstantMetadataArtifact(PathFragment rootRelativePath,
+ Root root);
+
+ /**
+ * Returns the artifact for the derived file {@code rootRelativePath},
+ * creating it if necessary, and setting the root of that artifact to
+ * {@code root}. The artifact will represent the output directory of a {@code Fileset}.
+ */
+ Artifact getFilesetArtifact(PathFragment rootRelativePath, Root root);
+
+ /**
+ * Returns the artifact for the specified tool.
+ */
+ Artifact getEmbeddedToolArtifact(String embeddedPath);
+
+ /**
+ * Returns the middleman factory associated with the build.
+ */
+ // TODO(bazel-team): remove this method and replace it with delegate methods.
+ MiddlemanFactory getMiddlemanFactory();
+
+ /**
+ * Returns the generating action for the given local artifact.
+ *
+ * If the artifact was created in another analysis environment (e.g. by a different configured
+ * target instance) or the artifact is a source artifact, it returns null.
+ */
+ Action getLocalGeneratingAction(Artifact artifact);
+
+ /**
+ * Returns the actions that were registered so far with this analysis environment, that is, all
+ * the actions that were created by the current target being analyzed.
+ */
+ Iterable<Action> getRegisteredActions();
+
+ /**
+ * Returns the Skyframe SkyFunction.Environment if available. Otherwise, null.
+ *
+ * <p>If you need to use this for something other than genquery, please think long and hard
+ * about that.
+ */
+ SkyFunction.Environment getSkyframeEnv();
+
+ /**
+ * Returns the Artifact that is used to hold the non-volatile workspace status for the current
+ * build request.
+ */
+ Artifact getStableWorkspaceStatusArtifact();
+
+ /**
+ * Returns the Artifact that is used to hold the volatile workspace status (e.g. build
+ * changelist) for the current build request.
+ */
+ Artifact getVolatileWorkspaceStatusArtifact();
+
+ /**
+ * Returns the Artifacts that contain the workspace status for the current build request.
+ *
+ * @param ruleContext the rule to use for error reporting and to determine the
+ * configuration
+ */
+ ImmutableList<Artifact> getBuildInfo(RuleContext ruleContext, BuildInfoKey key);
+
+ /**
+ * Returns the set of orphan Artifacts (i.e. Artifacts without generating action). Should only be
+ * called after the ConfiguredTarget is created.
+ */
+ ImmutableSet<Artifact> getOrphanArtifacts();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisFailureEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisFailureEvent.java
new file mode 100644
index 0000000..5064163
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisFailureEvent.java
@@ -0,0 +1,40 @@
+// 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.devtools.build.lib.syntax.Label;
+
+/**
+ * This event is fired during the build, when it becomes known that the analysis
+ * of a target cannot be completed because of an error in one of its
+ * dependencies.
+ */
+public class AnalysisFailureEvent {
+ private final LabelAndConfiguration failedTarget;
+ private final Label failureReason;
+
+ public AnalysisFailureEvent(LabelAndConfiguration failedTarget, Label failureReason) {
+ this.failedTarget = failedTarget;
+ this.failureReason = failureReason;
+ }
+
+ public LabelAndConfiguration getFailedTarget() {
+ return failedTarget;
+ }
+
+ public Label getFailureReason() {
+ return failureReason;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisHooks.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisHooks.java
new file mode 100644
index 0000000..82c4485
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisHooks.java
@@ -0,0 +1,36 @@
+// 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.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.pkgcache.PackageManager;
+
+/**
+ * This interface resolves target - configuration pairs to {@link ConfiguredTarget} instances.
+ *
+ * <p>This interface is used to provide analysis phase functionality to actions that need it in
+ * the execution phase.
+ */
+public interface AnalysisHooks {
+ /**
+ * Returns the package manager used during the analysis phase.
+ */
+ PackageManager getPackageManager();
+
+ /**
+ * Resolves an existing configured target. Returns null if it is not in the cache.
+ */
+ ConfiguredTarget getExistingConfiguredTarget(Target target, BuildConfiguration configuration);
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisPhaseCompleteEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisPhaseCompleteEvent.java
new file mode 100644
index 0000000..0d1e565
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisPhaseCompleteEvent.java
@@ -0,0 +1,59 @@
+// 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.collect.ImmutableList;
+
+import java.util.Collection;
+
+/**
+ * This event is fired after the analysis phase is complete.
+ */
+public class AnalysisPhaseCompleteEvent {
+
+ private final Collection<ConfiguredTarget> targets;
+ private final long timeInMs;
+ private int targetsVisited;
+
+ /**
+ * Construct the event.
+ * @param targets The set of active targets that remain.
+ */
+ public AnalysisPhaseCompleteEvent(Collection<? extends ConfiguredTarget> targets,
+ int targetsVisited, long timeInMs) {
+ this.timeInMs = timeInMs;
+ this.targets = ImmutableList.copyOf(targets);
+ this.targetsVisited = targetsVisited;
+ }
+
+ /**
+ * @return The set of active targets remaining, which is a subset
+ * of the targets we attempted to analyze.
+ */
+ public Collection<ConfiguredTarget> getTargets() {
+ return targets;
+ }
+
+ /**
+ * @return The number of targets freshly visited during analysis
+ */
+ public int getTargetsVisited() {
+ return targetsVisited;
+ }
+
+ public long getTimeInMs() {
+ return timeInMs;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisPhaseStartedEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisPhaseStartedEvent.java
new file mode 100644
index 0000000..fc97c60
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisPhaseStartedEvent.java
@@ -0,0 +1,51 @@
+// 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.Function;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.syntax.Label;
+
+import java.util.Collection;
+
+/**
+ * This event is fired before the analysis phase is started.
+ */
+public class AnalysisPhaseStartedEvent {
+
+ private final Iterable<Label> labels;
+
+ /**
+ * Construct the event.
+ * @param targets The set of active targets that remain.
+ */
+ public AnalysisPhaseStartedEvent(Collection<Target> targets) {
+ this.labels = Iterables.transform(targets, new Function<Target, Label>() {
+ @Override
+ public Label apply(Target input) {
+ return input.getLabel();
+ }
+ });
+ }
+
+ /**
+ * @return The set of active targets remaining, which is a subset
+ * of the targets we attempted to load.
+ */
+ public Iterable<Label> getLabels() {
+ return labels;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisUtils.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisUtils.java
new file mode 100644
index 0000000..2e4c251
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisUtils.java
@@ -0,0 +1,146 @@
+// 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.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+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.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.TriState;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Utility functions for use during analysis.
+ */
+public final class AnalysisUtils {
+
+ private AnalysisUtils() {
+ throw new IllegalStateException(); // utility class
+ }
+
+ /**
+ * Returns whether link stamping is enabled for a rule.
+ *
+ * <p>This returns false for unstampable rule classes and for rules in the
+ * host configuration. Otherwise it returns the value of the stamp attribute,
+ * or of the stamp option if the attribute value is -1.
+ */
+ public static boolean isStampingEnabled(RuleContext ruleContext) {
+ BuildConfiguration config = ruleContext.getConfiguration();
+ Rule rule = ruleContext.getRule();
+ if (config.isHostConfiguration()
+ || !rule.getRuleClassObject().hasAttr("stamp", Type.TRISTATE)) {
+ return false;
+ }
+ TriState stamp = ruleContext.attributes().get("stamp", Type.TRISTATE);
+ return stamp == TriState.YES || (stamp == TriState.AUTO && config.stampBinaries());
+ }
+
+ // TODO(bazel-team): These need Iterable<? extends TransitiveInfoCollection> because they need to
+ // be called with Iterable<ConfiguredTarget>. Once the configured target lockdown is complete, we
+ // can eliminate the "extends" clauses.
+ /**
+ * Returns the list of providers of the specified type from a set of transitive info
+ * collections.
+ */
+ public static <C extends TransitiveInfoProvider> Iterable<C> getProviders(
+ Iterable<? extends TransitiveInfoCollection> prerequisites, Class<C> provider) {
+ Collection<C> result = new ArrayList<>();
+ for (TransitiveInfoCollection prerequisite : prerequisites) {
+ C prerequisiteProvider = prerequisite.getProvider(provider);
+ if (prerequisiteProvider != null) {
+ result.add(prerequisiteProvider);
+ }
+ }
+ return ImmutableList.copyOf(result);
+ }
+
+ /**
+ * Returns the iterable of collections that have the specified provider.
+ */
+ public static <S extends TransitiveInfoCollection, C extends TransitiveInfoProvider> Iterable<S>
+ filterByProvider(Iterable<S> prerequisites, final Class<C> provider) {
+ return Iterables.filter(prerequisites, new Predicate<S>() {
+ @Override
+ public boolean apply(S target) {
+ return target.getProvider(provider) != null;
+ }
+ });
+ }
+
+ /**
+ * Returns the path of the associated manifest file for the path of a Fileset. Works for both
+ * exec paths and root relative paths.
+ */
+ public static PathFragment getManifestPathFromFilesetPath(PathFragment filesetDir) {
+ PathFragment manifestDir = filesetDir.replaceName("_" + filesetDir.getBaseName());
+ PathFragment outputManifestFrag = manifestDir.getRelative("MANIFEST");
+ return outputManifestFrag;
+ }
+
+ /**
+ * Returns the middleman artifact on the specified attribute of the specified rule, or an empty
+ * set if it does not exist.
+ */
+ public static NestedSet<Artifact> getMiddlemanFor(RuleContext rule, String attribute) {
+ TransitiveInfoCollection prereq = rule.getPrerequisite(attribute, Mode.HOST);
+ if (prereq == null) {
+ return NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+ }
+ MiddlemanProvider provider = prereq.getProvider(MiddlemanProvider.class);
+ if (provider == null) {
+ return NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+ }
+ return provider.getMiddlemanArtifact();
+ }
+
+ /**
+ * Returns a path fragment qualified by the rule name and unique fragment to
+ * disambiguate artifacts produced from the source file appearing in
+ * multiple rules.
+ *
+ * <p>For example "//pkg:target" -> "pkg/<fragment>/target.
+ */
+ public static PathFragment getUniqueDirectory(Label label, PathFragment fragment) {
+ return label.getPackageFragment().getRelative(fragment)
+ .getRelative(label.getName());
+ }
+
+ /**
+ * Checks that the given provider class either refers to an interface or to a value class.
+ */
+ public static <T extends TransitiveInfoProvider> void checkProvider(Class<T> clazz) {
+ if (!clazz.isInterface()) {
+ Preconditions.checkArgument(Modifier.isFinal(clazz.getModifiers()),
+ clazz.getName() + " has to be final");
+ Preconditions.checkArgument(clazz.isAnnotationPresent(Immutable.class),
+ clazz.getName() + " has to be tagged with @Immutable");
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/Aspect.java b/src/main/java/com/google/devtools/build/lib/analysis/Aspect.java
new file mode 100644
index 0000000..3f4a06e
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/Aspect.java
@@ -0,0 +1,82 @@
+// 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.ImmutableMap;
+import com.google.common.collect.UnmodifiableIterator;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Extra information about a configured target computed on request of a dependent.
+ *
+ * <p>Analogous to {@link ConfiguredTarget}: contains a bunch of transitive info providers, which
+ * are merged with the providers of the associated configured target before they are passed to
+ * the configured target factories that depend on the configured target to which this aspect is
+ * added.
+ *
+ * <p>Aspects are created alongside configured targets on request from dependents.
+ */
+@Immutable
+public final class Aspect implements Iterable<TransitiveInfoProvider> {
+ private final
+ ImmutableMap<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> providers;
+
+ private Aspect(
+ ImmutableMap<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> providers) {
+ this.providers = providers;
+ }
+
+ /**
+ * Returns the providers created by the aspect.
+ */
+ public ImmutableMap<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider>
+ getProviders() {
+ return providers;
+ }
+
+ @Override
+ public UnmodifiableIterator<TransitiveInfoProvider> iterator() {
+ return providers.values().iterator();
+ }
+
+ /**
+ * Builder for {@link Aspect}.
+ */
+ public static class Builder {
+ private final Map<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider>
+ providers = new LinkedHashMap<>();
+
+ /**
+ * Adds a provider to the aspect.
+ */
+ public Builder addProvider(
+ Class<? extends TransitiveInfoProvider> key, TransitiveInfoProvider value) {
+ Preconditions.checkNotNull(key);
+ Preconditions.checkNotNull(value);
+ AnalysisUtils.checkProvider(key);
+ Preconditions.checkState(!providers.containsKey(key));
+ providers.put(key, value);
+ return this;
+ }
+
+ public Aspect build() {
+ return new Aspect(ImmutableMap.copyOf(providers));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java b/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java
new file mode 100644
index 0000000..ad5756e
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java
@@ -0,0 +1,265 @@
+// 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 static com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition.DATA;
+import static com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition.HOST;
+import static com.google.devtools.build.lib.packages.Attribute.attr;
+import static com.google.devtools.build.lib.packages.Type.BOOLEAN;
+import static com.google.devtools.build.lib.packages.Type.DISTRIBUTIONS;
+import static com.google.devtools.build.lib.packages.Type.INTEGER;
+import static com.google.devtools.build.lib.packages.Type.LABEL;
+import static com.google.devtools.build.lib.packages.Type.LABEL_LIST;
+import static com.google.devtools.build.lib.packages.Type.LICENSE;
+import static com.google.devtools.build.lib.packages.Type.NODEP_LABEL_LIST;
+import static com.google.devtools.build.lib.packages.Type.STRING;
+import static com.google.devtools.build.lib.packages.Type.STRING_LIST;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.RunUnder;
+import com.google.devtools.build.lib.analysis.constraints.EnvironmentRule;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.Attribute.LateBoundLabel;
+import com.google.devtools.build.lib.packages.Attribute.LateBoundLabelList;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleClass.Builder;
+import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
+import com.google.devtools.build.lib.packages.TestSize;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.util.FileTypeSet;
+
+import java.util.List;
+
+/**
+ * Rule class definitions used by (almost) every rule.
+ */
+public class BaseRuleClasses {
+ /**
+ * Label of the pseudo-filegroup that contains all the targets that are needed
+ * for running tests in coverage mode.
+ */
+ private static final Label COVERAGE_SUPPORT_LABEL =
+ Label.parseAbsoluteUnchecked("//tools/defaults:coverage");
+
+ private static final Attribute.ComputedDefault obsoleteDefault =
+ new Attribute.ComputedDefault() {
+ @Override
+ public Object getDefault(AttributeMap rule) {
+ return rule.getPackageDefaultObsolete();
+ }
+ };
+
+ private static final Attribute.ComputedDefault testonlyDefault =
+ new Attribute.ComputedDefault() {
+ @Override
+ public Object getDefault(AttributeMap rule) {
+ return rule.getPackageDefaultTestOnly();
+ }
+ };
+
+ private static final Attribute.ComputedDefault deprecationDefault =
+ new Attribute.ComputedDefault() {
+ @Override
+ public Object getDefault(AttributeMap rule) {
+ return rule.getPackageDefaultDeprecation();
+ }
+ };
+
+ /**
+ * Implementation for the :action_listener attribute.
+ */
+ private static final LateBoundLabelList<BuildConfiguration> ACTION_LISTENER =
+ new LateBoundLabelList<BuildConfiguration>() {
+ @Override
+ public List<Label> getDefault(Rule rule, BuildConfiguration configuration) {
+ // action_listeners are special rules; they tell the build system to add extra_actions to
+ // existing rules. As such they need an edge to every ConfiguredTarget with the limitation
+ // that they only run on the target configuration and should not operate on action_listeners
+ // and extra_actions themselves (to avoid cycles).
+ return configuration.getActionListeners();
+ }
+ };
+
+ private static final LateBoundLabelList<BuildConfiguration> COVERAGE_SUPPORT =
+ new LateBoundLabelList<BuildConfiguration>(ImmutableList.of(COVERAGE_SUPPORT_LABEL)) {
+ @Override
+ public List<Label> getDefault(Rule rule, BuildConfiguration configuration) {
+ return configuration.isCodeCoverageEnabled()
+ ? ImmutableList.<Label>copyOf(configuration.getCoverageLabels())
+ : ImmutableList.<Label>of();
+ }
+ };
+
+ private static final LateBoundLabelList<BuildConfiguration> COVERAGE_REPORT_GENERATOR =
+ new LateBoundLabelList<BuildConfiguration>(ImmutableList.of(COVERAGE_SUPPORT_LABEL)) {
+ @Override
+ public List<Label> getDefault(Rule rule, BuildConfiguration configuration) {
+ return configuration.isCodeCoverageEnabled()
+ ? ImmutableList.<Label>copyOf(configuration.getCoverageReportGeneratorLabels())
+ : ImmutableList.<Label>of();
+ }
+ };
+
+ /**
+ * Implementation for the :run_under attribute.
+ */
+ private static final LateBoundLabel<BuildConfiguration> RUN_UNDER =
+ new LateBoundLabel<BuildConfiguration>() {
+ @Override
+ public Label getDefault(Rule rule, BuildConfiguration configuration) {
+ RunUnder runUnder = configuration.getRunUnder();
+ return runUnder == null ? null : runUnder.getLabel();
+ }
+ };
+
+ /**
+ * A base rule for all test rules.
+ */
+ @BlazeRule(name = "$test_base_rule",
+ type = RuleClassType.ABSTRACT)
+ public static final class TestBaseRule implements RuleDefinition {
+ @Override
+ public RuleClass build(Builder builder, RuleDefinitionEnvironment env) {
+ return builder
+ .add(attr("size", STRING).value("medium").taggable()
+ .nonconfigurable("policy decision: should be consistent across configurations"))
+ .add(attr("timeout", STRING).taggable()
+ .nonconfigurable("policy decision: should be consistent across configurations")
+ .value(new Attribute.ComputedDefault() {
+ @Override
+ public Object getDefault(AttributeMap rule) {
+ TestSize size = TestSize.getTestSize(rule.get("size", Type.STRING));
+ if (size != null) {
+ String timeout = size.getDefaultTimeout().toString();
+ if (timeout != null) {
+ return timeout;
+ }
+ }
+ return "illegal";
+ }
+ }))
+ .add(attr("flaky", BOOLEAN).value(false).taggable()
+ .nonconfigurable("policy decision: should be consistent across configurations"))
+ .add(attr("shard_count", INTEGER).value(-1))
+ .add(attr("local", BOOLEAN).value(false).taggable()
+ .nonconfigurable("policy decision: should be consistent across configurations"))
+ .add(attr("args", STRING_LIST)
+ .nonconfigurable("policy decision: should be consistent across configurations"))
+ .add(attr("$test_runtime", LABEL_LIST).cfg(HOST).value(ImmutableList.of(
+ env.getLabel("//tools/test:runtime"))))
+
+ // TODO(bazel-team): TestActions may need to be run with coverage, so all tests
+ // implicitly depend on crosstool, which provides gcov. We could add gcov to
+ // InstrumentedFilesProvider.getInstrumentationMetadataFiles() (or a new method) for
+ // all the test rules that have C++ in their transitive closure. Then this could go.
+ .add(attr(":coverage_support", LABEL_LIST).cfg(HOST).value(COVERAGE_SUPPORT))
+ .add(attr(":coverage_report_generator", LABEL_LIST).cfg(HOST)
+ .value(COVERAGE_REPORT_GENERATOR))
+
+ // The target itself and run_under both run on the same machine. We use the DATA config
+ // here because the run_under acts like a data dependency (e.g. no LIPO optimization).
+ .add(attr(":run_under", LABEL).cfg(DATA).value(RUN_UNDER))
+ .build();
+ }
+ }
+
+ /**
+ * Share common attributes across both base and Skylark base rules.
+ */
+ public static RuleClass.Builder commonCoreAndSkylarkAttributes(RuleClass.Builder builder) {
+ return builder
+ // The visibility attribute is special: it is a nodep label, and loading the
+ // necessary package groups is handled by {@link LabelVisitor#visitTargetVisibility}.
+ // Package groups always have the null configuration so that they are not duplicated
+ // needlessly.
+ .add(attr("visibility", NODEP_LABEL_LIST).orderIndependent().cfg(HOST)
+ .nonconfigurable("special attribute integrated more deeply into Bazel's core logic"))
+ .add(attr("deprecation", STRING).value(deprecationDefault)
+ .nonconfigurable("Used in core loading phase logic with no access to configs"))
+ .add(attr("tags", STRING_LIST).orderIndependent().taggable()
+ .nonconfigurable("low-level attribute, used in TargetUtils without configurations"))
+ .add(attr("generator_name", STRING).undocumented("internal"))
+ .add(attr("generator_function", STRING).undocumented("internal"))
+ .add(attr("testonly", BOOLEAN).value(testonlyDefault)
+ .nonconfigurable("policy decision: rules testability should be consistent"))
+ .add(attr(RuleClass.COMPATIBLE_ENVIRONMENT_ATTR, LABEL_LIST)
+ .allowedRuleClasses(EnvironmentRule.RULE_NAME)
+ .cfg(Attribute.ConfigurationTransition.HOST)
+ .allowedFileTypes(FileTypeSet.NO_FILE)
+ .undocumented("not yet released"))
+ .add(attr(RuleClass.RESTRICTED_ENVIRONMENT_ATTR, LABEL_LIST)
+ .allowedRuleClasses(EnvironmentRule.RULE_NAME)
+ .cfg(Attribute.ConfigurationTransition.HOST)
+ .allowedFileTypes(FileTypeSet.NO_FILE)
+ .undocumented("not yet released"));
+ }
+
+ /**
+ * Common parts of rules.
+ */
+ @BlazeRule(name = "$base_rule",
+ type = RuleClassType.ABSTRACT)
+ public static final class BaseRule implements RuleDefinition {
+ @Override
+ public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) {
+ return commonCoreAndSkylarkAttributes(builder)
+ // The name attribute is handled specially, so it does not appear here.
+ //
+ // Aggregates the labels of all {@link ConfigRuleClasses} rules this rule uses (e.g.
+ // keys for configurable attributes). This is specially populated in
+ // {@RuleClass#populateRuleAttributeValues}.
+ //
+ // This attribute is not needed for actual builds. Its main purpose is so query's
+ // proto/XML output includes the labels of config dependencies, so, e.g., depserver
+ // reverse dependency lookups remain accurate. These can't just be added to the
+ // attribute definitions proto/XML queries already output because not all attributes
+ // contain labels.
+ //
+ // Builds and Blaze-interactive queries don't need this because they find dependencies
+ // through direct Rule label visitation, which already factors these in.
+ .add(attr("$config_dependencies", LABEL_LIST)
+ .nonconfigurable("not intended for actual builds"))
+ .add(attr("licenses", LICENSE)
+ .nonconfigurable("Used in core loading phase logic with no access to configs"))
+ .add(attr("distribs", DISTRIBUTIONS)
+ .nonconfigurable("Used in core loading phase logic with no access to configs"))
+ .add(attr("obsolete", BOOLEAN).value(obsoleteDefault)
+ .nonconfigurable("Used in core loading phase logic with no access to configs"))
+ .add(attr(":action_listener", LABEL_LIST).cfg(HOST).value(ACTION_LISTENER))
+ .build();
+ }
+ }
+
+ /**
+ * Common ancestor class for all rules.
+ */
+ @BlazeRule(name = "$rule",
+ type = RuleClassType.ABSTRACT,
+ ancestors = { BaseRule.class })
+ public static final class RuleBase implements RuleDefinition {
+ @Override
+ public RuleClass build(Builder builder, RuleDefinitionEnvironment env) {
+ return builder
+ .add(attr("deps", LABEL_LIST).legacyAllowAnyFileType())
+ .add(attr("data", LABEL_LIST).cfg(DATA).allowedFileTypes(FileTypeSet.ANY_FILE))
+ .build();
+ }
+ }
+
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BaselineCoverageArtifactsProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/BaselineCoverageArtifactsProvider.java
new file mode 100644
index 0000000..ab74581
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BaselineCoverageArtifactsProvider.java
@@ -0,0 +1,42 @@
+// 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.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+/**
+ * A {@link TransitiveInfoProvider} that has baseline coverage artifacts.
+ */
+@Immutable
+public final class BaselineCoverageArtifactsProvider implements TransitiveInfoProvider {
+ private final ImmutableList<Artifact> baselineCoverageArtifacts;
+
+ public BaselineCoverageArtifactsProvider(ImmutableList<Artifact> baselineCoverageArtifacts) {
+ this.baselineCoverageArtifacts = baselineCoverageArtifacts;
+ }
+
+ /**
+ * Returns a set of baseline coverage artifacts for a given set of configured targets.
+ *
+ * <p>These artifacts represent "empty" code coverage data for non-test libraries and binaries and
+ * used to establish correct baseline when calculating code coverage ratios since they would cover
+ * completely non-tested code as well.
+ */
+ public ImmutableList<Artifact> getBaselineCoverageArtifacts() {
+ return baselineCoverageArtifacts;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BlazeDirectories.java b/src/main/java/com/google/devtools/build/lib/analysis/BlazeDirectories.java
new file mode 100644
index 0000000..38a8d41
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BlazeDirectories.java
@@ -0,0 +1,183 @@
+// 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.devtools.build.lib.Constants;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.util.StringCanonicalizer;
+import com.google.devtools.build.lib.vfs.FileSystem;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.io.Serializable;
+
+import javax.annotation.Nullable;
+
+/**
+ * Encapsulation of all of the interesting top-level directories in any Blaze application.
+ *
+ * <p>The <code>installBase</code> is the directory where the Blaze binary has been installed.The
+ * <code>workspace</code> is the top-level directory in the user's client (possibly read-only).The
+ * <code>outputBase</code> is the directory below which Blaze puts all its state. The
+ * <code>execRoot</code> is the working directory for all spawned tools, which is generally below
+ * <code>outputBase</code>.
+ *
+ * <p>There is a 1:1 correspondence between a running Blaze instance and an output base directory;
+ * however, multiple Blaze instances may compile code that's in the same workspace, even on the same
+ * machine. If the user does not qualify an output base directory, the startup code will derive it
+ * deterministically from the workspace. Note also that while the Blaze server process runs with the
+ * workspace directory as its working directory, the client process may have a different working
+ * directory, typically a subdirectory.
+ *
+ * <p>Do not put shortcuts to specific files here!
+ */
+@Immutable
+public final class BlazeDirectories implements Serializable {
+
+ // Output directory name, relative to the execRoot.
+ // TODO(bazel-team): (2011) make this private?
+ public static final String RELATIVE_OUTPUT_PATH = StringCanonicalizer.intern(
+ Constants.PRODUCT_NAME + "-out");
+
+ // Include directory name, relative to execRoot/blaze-out/configuration.
+ public static final String RELATIVE_INCLUDE_DIR = StringCanonicalizer.intern("include");
+
+ private final Path installBase; // Where Blaze gets unpacked
+ private final Path workspace; // Workspace root and server CWD
+ private final Path outputBase; // The root of the temp and output trees
+ private final Path execRoot; // the root of all build actions
+
+ // These two are kept to avoid creating new objects every time they are accessed. This showed up
+ // in a profiler.
+ private final Path outputPath;
+ private final Path localOutputPath;
+
+ public BlazeDirectories(Path installBase, Path outputBase, @Nullable Path workspace) {
+ this.installBase = installBase;
+ this.workspace = workspace;
+ this.outputBase = outputBase;
+ if (this.workspace == null) {
+ // TODO(bazel-team): this should be null, but at the moment there is a lot of code that
+ // depends on it being non-null.
+ this.execRoot = outputBase.getChild("default-exec-root");
+ } else {
+ this.execRoot = outputBase.getChild(workspace.getBaseName());
+ }
+ this.outputPath = execRoot.getRelative(RELATIVE_OUTPUT_PATH);
+ Preconditions.checkState(this.workspace == null || outputPath.asFragment().equals(
+ outputPathFromOutputBase(outputBase.asFragment(), workspace.asFragment())));
+ this.localOutputPath = outputBase.getRelative(BlazeDirectories.RELATIVE_OUTPUT_PATH);
+ }
+
+ /**
+ * Returns the Filesystem that all of our directories belong to. Handy for
+ * resolving absolute paths.
+ */
+ public FileSystem getFileSystem() {
+ return installBase.getFileSystem();
+ }
+
+ /**
+ * Returns the installation base directory. Currently used by info command only.
+ */
+ public Path getInstallBase() {
+ return installBase;
+ }
+
+ /**
+ * Returns the workspace directory, which is also the working dir of the server.
+ */
+ public Path getWorkspace() {
+ return workspace;
+ }
+
+ /**
+ * Returns if the workspace directory is a valid workspace.
+ */
+ public boolean inWorkspace() {
+ return this.workspace != null;
+ }
+
+ /**
+ * Returns the base of the output tree, which hosts all build and scratch
+ * output for a user and workspace.
+ */
+ public Path getOutputBase() {
+ return outputBase;
+ }
+
+ /**
+ * Returns the execution root. This is the directory underneath which Blaze builds the source
+ * symlink forest, to represent the merged view of different workspaces specified
+ * with --package_path.
+ */
+ public Path getExecRoot() {
+ return execRoot;
+ }
+
+ /**
+ * Returns the output path used by this Blaze instance.
+ */
+ public Path getOutputPath() {
+ return outputPath;
+ }
+
+ /**
+ * @param outputBase the outputBase as a path fragment.
+ * @param workspace the workspace as a path fragment.
+ * @return the outputPath as a path fragment, given the outputBase.
+ */
+ public static PathFragment outputPathFromOutputBase(
+ PathFragment outputBase, PathFragment workspace) {
+ if (workspace.equals(PathFragment.EMPTY_FRAGMENT)) {
+ return outputBase;
+ }
+ return outputBase.getRelative(workspace.getBaseName() + "/" + RELATIVE_OUTPUT_PATH);
+ }
+
+ /**
+ * Returns the local output path used by this Blaze instance.
+ */
+ public Path getLocalOutputPath() {
+ return localOutputPath;
+ }
+
+ /**
+ * Returns the directory where the stdout/stderr for actions can be stored
+ * temporarily for a build. If the directory already exists, the directory
+ * is cleaned.
+ */
+ public Path getActionConsoleOutputDirectory() {
+ return getOutputBase().getRelative("action_outs");
+ }
+
+ /**
+ * Returns the installed embedded binaries directory, under the shared
+ * installBase location.
+ */
+ public Path getEmbeddedBinariesRoot() {
+ return installBase.getChild("_embedded_binaries");
+ }
+
+ /**
+ * Returns the configuration-independent root where the build-data should be placed, given the
+ * {@link BlazeDirectories} of this server instance. Nothing else should be placed here.
+ */
+ public Root getBuildDataDirectory() {
+ return Root.asDerivedRoot(getExecRoot(), getOutputPath());
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BlazeRule.java b/src/main/java/com/google/devtools/build/lib/analysis/BlazeRule.java
new file mode 100644
index 0000000..c349e65
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BlazeRule.java
@@ -0,0 +1,55 @@
+// 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.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation for rule classes.
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface BlazeRule {
+ /**
+ * The name of the rule, as it appears in the BUILD file. If it starts with
+ * '$', the rule will be hidden from users and will only be usable from
+ * inside Blaze.
+ */
+ String name();
+
+ /**
+ * The type of the rule. It can be an abstract rule, a normal rule or a test
+ * rule. If the rule type is abstract, the configured class must not be set.
+ */
+ RuleClassType type() default RuleClassType.NORMAL;
+
+ /**
+ * The {@link RuleConfiguredTargetFactory} class that implements this rule. If the rule is
+ * abstract, this must not be set.
+ */
+ Class<? extends RuleConfiguredTargetFactory> factoryClass()
+ default RuleConfiguredTargetFactory.class;
+
+ /**
+ * The list of other rule classes this rule inherits from.
+ */
+ Class<? extends RuleDefinition>[] ancestors() default {};
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BlazeVersionInfo.java b/src/main/java/com/google/devtools/build/lib/analysis/BlazeVersionInfo.java
new file mode 100644
index 0000000..d5b5f94
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BlazeVersionInfo.java
@@ -0,0 +1,113 @@
+// 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.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.devtools.build.lib.util.StringUtilities;
+
+import java.util.Map;
+import java.util.logging.Logger;
+
+/**
+ * Determines the version information of the current process.
+ *
+ * <p>The version information is a dictionary mapping from string keys to string values. For
+ * build stamping, it should have the key "Build label", which contains among others a
+ * XXXXXXX-YYYY.MM.DD string to indicate the version of the release. If no data is available
+ * (eg. when running non-released version), {@link #isAvailable()} returns false.
+ */
+public class BlazeVersionInfo {
+ private final Map<String, String> buildData = Maps.newTreeMap();
+ private static BlazeVersionInfo instance = null;
+ private static final String BUILD_LABEL = "Build label";
+
+ private static final Logger LOG = Logger.getLogger(BlazeVersionInfo.class.getName());
+
+ public BlazeVersionInfo(Map<String, String> info) {
+ buildData.putAll(info);
+ }
+
+ /**
+ * Accessor method for BlazeVersionInfo singleton.
+ *
+ * <p>If setBuildInfo was not called, returns an empty BlazeVersionInfo instance, which should
+ * not be persisted.
+ */
+ public static synchronized BlazeVersionInfo instance() {
+ if (instance == null) {
+ return new BlazeVersionInfo(ImmutableMap.<String, String>of());
+ }
+ return instance;
+ }
+
+ private static void logVersionInfo(BlazeVersionInfo info) {
+ if (info.getSummary() == null) {
+ LOG.warning("Blaze release version information not available");
+ } else {
+ LOG.info("Blaze version info: " + info.getSummary());
+ }
+ }
+
+ /**
+ * Sets build info.
+ *
+ * <p>This should be called once in the program execution, as early soon as possible, so we
+ * can have the version information even before modules are initialized.
+ */
+ public static synchronized void setBuildInfo(Map<String, String> info) {
+ if (instance != null) {
+ throw new IllegalStateException("setBuildInfo called twice.");
+ }
+ instance = new BlazeVersionInfo(info);
+ logVersionInfo(instance);
+ }
+
+ /**
+ * Indicates whether version information is available.
+ */
+ public boolean isAvailable() {
+ return !buildData.isEmpty();
+ }
+
+ /**
+ * Returns the summary which gets displayed in the 'version' command.
+ * The summary is a list of formatted key / value pairs.
+ */
+ public String getSummary() {
+ if (buildData.isEmpty()) {
+ return null;
+ }
+ return StringUtilities.layoutTable(buildData);
+ }
+
+ /**
+ * Returns true iff this binary is released--that is, a
+ * binary built with a release label.
+ */
+ public boolean isReleasedBlaze() {
+ String buildLabel = buildData.get(BUILD_LABEL);
+ return buildLabel != null && buildLabel.length() > 0;
+ }
+
+ /**
+ * Returns the release label, if any, or "development version".
+ */
+ public String getReleaseName() {
+ String buildLabel = buildData.get(BUILD_LABEL);
+ return (buildLabel != null && buildLabel.length() > 0)
+ ? "release " + buildLabel
+ : "development version";
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BuildInfoEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/BuildInfoEvent.java
new file mode 100644
index 0000000..59d1514
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BuildInfoEvent.java
@@ -0,0 +1,40 @@
+// 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.collect.ImmutableMap;
+
+import java.util.Map;
+
+/**
+ * This event is fired once build info data is available.
+ */
+public final class BuildInfoEvent {
+ private final Map<String, String> buildInfoMap;
+
+ /**
+ * Construct the event from a map.
+ */
+ public BuildInfoEvent(Map<String, String> buildInfo) {
+ buildInfoMap = ImmutableMap.copyOf(buildInfo);
+ }
+
+ /**
+ * Return immutable map populated with build info key/value pairs.
+ */
+ public Map<String, String> getBuildInfoMap() {
+ return buildInfoMap;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BuildInfoHelper.java b/src/main/java/com/google/devtools/build/lib/analysis/BuildInfoHelper.java
new file mode 100644
index 0000000..755df9f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BuildInfoHelper.java
@@ -0,0 +1,31 @@
+// 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.devtools.build.lib.actions.AbstractActionOwner;
+import com.google.devtools.build.lib.actions.ActionOwner;
+
+// TODO(bazel-team): move BUILD_INFO_ACTION_OWNER somewhere else and remove this class.
+/**
+ * Helper class for the CompatibleWriteBuildInfoAction, which holds the
+ * methods for generating build information.
+ * Abstracted away to allow non-action code to also generate build info under
+ * --nobuild or --check_up_to_date.
+ */
+public abstract class BuildInfoHelper {
+ /** ActionOwner for BuildInfoActions. */
+ public static final ActionOwner BUILD_INFO_ACTION_OWNER =
+ AbstractActionOwner.SYSTEM_ACTION_OWNER;
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
new file mode 100644
index 0000000..052d0a2
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
@@ -0,0 +1,1056 @@
+// 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.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.cache.LoadingCache;
+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.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.Sets;
+import com.google.common.eventbus.EventBus;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionGraph;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ArtifactFactory;
+import com.google.devtools.build.lib.actions.PackageRootResolver;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.analysis.DependencyResolver.Dependency;
+import com.google.devtools.build.lib.analysis.ExtraActionArtifactsProvider.ExtraArtifactSet;
+import com.google.devtools.build.lib.analysis.config.BinTools;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection;
+import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
+import com.google.devtools.build.lib.events.DelegatingEventHandler;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.events.EventKind;
+import com.google.devtools.build.lib.events.StoredEventHandler;
+import com.google.devtools.build.lib.events.WarningsAsErrorsEventHandler;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.NoSuchPackageException;
+import com.google.devtools.build.lib.packages.NoSuchTargetException;
+import com.google.devtools.build.lib.packages.NoSuchThingException;
+import com.google.devtools.build.lib.packages.PackageIdentifier;
+import com.google.devtools.build.lib.packages.PackageSpecification;
+import com.google.devtools.build.lib.packages.RawAttributeMapper;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.packages.TargetUtils;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.pkgcache.LoadingPhaseRunner.LoadingResult;
+import com.google.devtools.build.lib.pkgcache.PackageManager;
+import com.google.devtools.build.lib.rules.test.CoverageReportActionFactory;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
+import com.google.devtools.build.lib.skyframe.CoverageReportValue;
+import com.google.devtools.build.lib.skyframe.SkyframeBuildView;
+import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.util.RegexFilter;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionsBase;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+
+/**
+ * <p>The BuildView presents a semantically-consistent and transitively-closed
+ * dependency graph for some set of packages.
+ *
+ * <h2>Package design</h2>
+ *
+ * <p>This package contains the Blaze dependency analysis framework (aka
+ * "analysis phase"). The goal of this code is to perform semantic analysis of
+ * all of the build targets required for a given build, to report
+ * errors/warnings for any problems in the input, and to construct an "action
+ * graph" (see {@code lib.actions} package) correctly representing the work to
+ * be done during the execution phase of the build.
+ *
+ * <p><b>Configurations</b> the inputs to a build come from two sources: the
+ * intrinsic inputs, specified in the BUILD file, are called <em>targets</em>.
+ * The environmental inputs, coming from the build tool, the command-line, or
+ * configuration files, are called the <em>configuration</em>. Only when a
+ * target and a configuration are combined is there sufficient information to
+ * perform a build. </p>
+ *
+ * <p>Targets are implemented by the {@link Target} hierarchy in the {@code
+ * lib.packages} code. Configurations are implemented by {@link
+ * BuildConfiguration}. The pair of these together is represented by an
+ * instance of class {@link ConfiguredTarget}; this is the root of a hierarchy
+ * with different implementations for each kind of target: source file, derived
+ * file, rules, etc.
+ *
+ * <p>The framework code in this package (as opposed to its subpackages) is
+ * responsible for constructing the {@code ConfiguredTarget} graph for a given
+ * target and configuration, taking care of such issues as:
+ * <ul>
+ * <li>caching common subgraphs.
+ * <li>detecting and reporting cycles.
+ * <li>correct propagation of errors through the graph.
+ * <li>reporting universal errors, such as dependencies from production code
+ * to tests, or to experimental branches.
+ * <li>capturing and replaying errors.
+ * <li>maintaining the graph from one build to the next to
+ * avoid unnecessary recomputation.
+ * <li>checking software licenses.
+ * </ul>
+ *
+ * <p>See also {@link ConfiguredTarget} which documents some important
+ * invariants.
+ */
+public class BuildView {
+
+ /**
+ * Options that affect the <i>mechanism</i> of analysis. These are distinct from {@link
+ * com.google.devtools.build.lib.analysis.config.BuildOptions}, which affect the <i>value</i>
+ * of a BuildConfiguration.
+ */
+ public static class Options extends OptionsBase {
+
+ @Option(name = "keep_going",
+ abbrev = 'k',
+ defaultValue = "false",
+ category = "strategy",
+ help = "Continue as much as possible after an error. While the "
+ + "target that failed, and those that depend on it, cannot be "
+ + "analyzed (or built), the other prerequisites of these "
+ + "targets can be analyzed (or built) all the same.")
+ public boolean keepGoing;
+
+ @Option(name = "analysis_warnings_as_errors",
+ defaultValue = "false",
+ category = "strategy",
+ help = "Treat visible analysis warnings as errors.")
+ public boolean analysisWarningsAsErrors;
+
+ @Option(name = "discard_analysis_cache",
+ defaultValue = "false",
+ category = "strategy",
+ help = "Discard the analysis cache immediately after the analysis phase completes. "
+ + "Reduces memory usage by ~10%, but makes further incremental builds slower.")
+ public boolean discardAnalysisCache;
+
+ @Option(name = "keep_forward_graph",
+ deprecationWarning = "keep_forward_graph is now a no-op and will be removed in an "
+ + "upcoming Blaze release",
+ defaultValue = "false",
+ category = "undocumented",
+ help = "Cache the forward action graph across builds for faster "
+ + "incremental rebuilds. May slightly increase memory while Blaze "
+ + "server is idle."
+ )
+ public boolean keepForwardGraph;
+
+ @Option(name = "experimental_extra_action_filter",
+ defaultValue = "",
+ category = "experimental",
+ converter = RegexFilter.RegexFilterConverter.class,
+ help = "Filters set of targets to schedule extra_actions for.")
+ public RegexFilter extraActionFilter;
+
+ @Option(name = "experimental_extra_action_top_level_only",
+ defaultValue = "false",
+ category = "experimental",
+ help = "Only schedules extra_actions for top level targets.")
+ public boolean extraActionTopLevelOnly;
+
+ @Option(name = "version_window_for_dirty_node_gc",
+ defaultValue = "0",
+ category = "undocumented",
+ help = "Nodes that have been dirty for more than this many versions will be deleted"
+ + " from the graph upon the next update. Values must be non-negative long integers,"
+ + " or -1 indicating the maximum possible window.")
+ public long versionWindowForDirtyNodeGc;
+ }
+
+ private static Logger LOG = Logger.getLogger(BuildView.class.getName());
+
+ private final BlazeDirectories directories;
+
+ private final SkyframeExecutor skyframeExecutor;
+ private final SkyframeBuildView skyframeBuildView;
+
+ private final PackageManager packageManager;
+
+ private final BinTools binTools;
+
+ private BuildConfigurationCollection configurations;
+
+ private final ConfiguredRuleClassProvider ruleClassProvider;
+
+ private final ArtifactFactory artifactFactory;
+
+ /**
+ * A factory class to create the coverage report action. May be null.
+ */
+ @Nullable private final CoverageReportActionFactory coverageReportActionFactory;
+
+ /**
+ * A union of package roots of all previous incremental analysis results. This is used to detect
+ * changes of package roots between incremental analysis instances.
+ */
+ private final Map<PackageIdentifier, Path> cumulativePackageRoots = new HashMap<>();
+
+ /**
+ * Used only for testing that we clear Skyframe caches correctly.
+ * TODO(bazel-team): Remove this once we get rid of legacy Skyframe synchronization.
+ */
+ private boolean skyframeCacheWasInvalidated = false;
+
+ /**
+ * If the last build was executed with {@code Options#discard_analysis_cache} and we are not
+ * running Skyframe full, we should clear the legacy data since it is out-of-sync.
+ */
+ private boolean skyframeAnalysisWasDiscarded = false;
+
+ @VisibleForTesting
+ public Set<SkyKey> getSkyframeEvaluatedTargetKeysForTesting() {
+ return skyframeBuildView.getEvaluatedTargetKeys();
+ }
+
+ /** The number of targets freshly evaluated in the last analysis run. */
+ public int getTargetsVisited() {
+ return skyframeBuildView.getEvaluatedTargetKeys().size();
+ }
+
+ /**
+ * Returns true iff Skyframe was invalidated during the analysis phase.
+ * TODO(bazel-team): Remove this once we do not need to keep legacy in sync with Skyframe.
+ */
+ @VisibleForTesting
+ boolean wasSkyframeCacheInvalidatedDuringAnalysis() {
+ return skyframeCacheWasInvalidated;
+ }
+
+ public BuildView(BlazeDirectories directories, PackageManager packageManager,
+ ConfiguredRuleClassProvider ruleClassProvider,
+ SkyframeExecutor skyframeExecutor,
+ BinTools binTools, CoverageReportActionFactory coverageReportActionFactory) {
+ this.directories = directories;
+ this.packageManager = packageManager;
+ this.binTools = binTools;
+ this.coverageReportActionFactory = coverageReportActionFactory;
+ this.artifactFactory = new ArtifactFactory(directories.getExecRoot());
+ this.ruleClassProvider = ruleClassProvider;
+ this.skyframeExecutor = Preconditions.checkNotNull(skyframeExecutor);
+ this.skyframeBuildView =
+ new SkyframeBuildView(
+ new ConfiguredTargetFactory(ruleClassProvider),
+ artifactFactory,
+ skyframeExecutor,
+ new Runnable() {
+ @Override
+ public void run() {
+ clear();
+ }
+ },
+ binTools);
+ skyframeExecutor.setSkyframeBuildView(skyframeBuildView);
+ }
+
+ /** Returns the action graph. */
+ public ActionGraph getActionGraph() {
+ return new ActionGraph() {
+ @Override
+ public Action getGeneratingAction(Artifact artifact) {
+ return skyframeExecutor.getGeneratingAction(artifact);
+ }
+ };
+ }
+
+ /**
+ * Returns whether the given configured target has errors.
+ */
+ @VisibleForTesting
+ public boolean hasErrors(ConfiguredTarget configuredTarget) {
+ return configuredTarget == null;
+ }
+
+ /**
+ * Sets the configurations. Not thread-safe. DO NOT CALL except from tests!
+ */
+ @VisibleForTesting
+ void setConfigurationsForTesting(BuildConfigurationCollection configurations) {
+ this.configurations = configurations;
+ }
+
+ public BuildConfigurationCollection getConfigurationCollection() {
+ return configurations;
+ }
+
+ /**
+ * Clear the graphs of ConfiguredTargets and Artifacts.
+ */
+ @VisibleForTesting
+ public void clear() {
+ cumulativePackageRoots.clear();
+ artifactFactory.clear();
+ }
+
+ public ArtifactFactory getArtifactFactory() {
+ return artifactFactory;
+ }
+
+ @VisibleForTesting
+ WorkspaceStatusAction getLastWorkspaceBuildInfoActionForTesting() {
+ return skyframeExecutor.getLastWorkspaceStatusActionForTesting();
+ }
+
+ /**
+ * Returns a corresponding ConfiguredTarget, if one exists; otherwise throws an {@link
+ * NoSuchConfiguredTargetException}.
+ */
+ @ThreadSafe
+ private ConfiguredTarget getConfiguredTarget(Target target, BuildConfiguration config)
+ throws NoSuchConfiguredTargetException {
+ ConfiguredTarget result =
+ getExistingConfiguredTarget(target.getLabel(), config);
+ if (result == null) {
+ throw new NoSuchConfiguredTargetException(target.getLabel(), config);
+ }
+ return result;
+ }
+
+ /**
+ * Obtains a {@link ConfiguredTarget} given a {@code label}, by delegating
+ * to the package cache and
+ * {@link #getConfiguredTarget(Target, BuildConfiguration)}.
+ */
+ public ConfiguredTarget getConfiguredTarget(Label label, BuildConfiguration config)
+ throws NoSuchPackageException, NoSuchTargetException, NoSuchConfiguredTargetException {
+ return getConfiguredTarget(packageManager.getLoadedTarget(label), config);
+ }
+
+ public Iterable<ConfiguredTarget> getDirectPrerequisites(ConfiguredTarget ct) {
+ return getDirectPrerequisites(ct, null);
+ }
+
+ public Iterable<ConfiguredTarget> getDirectPrerequisites(ConfiguredTarget ct,
+ @Nullable final LoadingCache<Label, Target> targetCache) {
+ if (!(ct.getTarget() instanceof Rule)) {
+ return ImmutableList.of();
+ }
+
+ class SilentDependencyResolver extends DependencyResolver {
+ @Override
+ protected void invalidVisibilityReferenceHook(TargetAndConfiguration node, Label label) {
+ // The error must have been reported already during analysis.
+ }
+
+ @Override
+ protected void invalidPackageGroupReferenceHook(TargetAndConfiguration node, Label label) {
+ // The error must have been reported already during analysis.
+ }
+
+ @Override
+ protected Target getTarget(Label label) throws NoSuchThingException {
+ if (targetCache == null) {
+ return packageManager.getLoadedTarget(label);
+ }
+
+ try {
+ return targetCache.get(label);
+ } catch (ExecutionException e) {
+ // All lookups should succeed because we should not be looking up any targets in error.
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
+ DependencyResolver dependencyResolver = new SilentDependencyResolver();
+ TargetAndConfiguration ctgNode =
+ new TargetAndConfiguration(ct.getTarget(), ct.getConfiguration());
+ return skyframeExecutor.getConfiguredTargets(
+ dependencyResolver.dependentNodes(ctgNode, getConfigurableAttributeKeys(ctgNode)));
+ }
+
+ /**
+ * Returns ConfigMatchingProvider instances corresponding to the configurable attribute keys
+ * present in this rule's attributes.
+ */
+ private Set<ConfigMatchingProvider> getConfigurableAttributeKeys(TargetAndConfiguration ctg) {
+ if (!(ctg.getTarget() instanceof Rule)) {
+ return ImmutableSet.of();
+ }
+ Rule rule = (Rule) ctg.getTarget();
+ ImmutableSet.Builder<ConfigMatchingProvider> keys = ImmutableSet.builder();
+ RawAttributeMapper mapper = RawAttributeMapper.of(rule);
+ for (Attribute attribute : rule.getAttributes()) {
+ for (Label label : mapper.getConfigurabilityKeys(attribute.getName(), attribute.getType())) {
+ if (Type.Selector.isReservedLabel(label)) {
+ continue;
+ }
+ try {
+ ConfiguredTarget ct = getConfiguredTarget(label, ctg.getConfiguration());
+ keys.add(Preconditions.checkNotNull(ct.getProvider(ConfigMatchingProvider.class)));
+ } catch (NoSuchPackageException e) {
+ // All lookups should succeed because we should not be looking up any targets in error.
+ throw new IllegalStateException(e);
+ } catch (NoSuchTargetException e) {
+ // All lookups should succeed because we should not be looking up any targets in error.
+ throw new IllegalStateException(e);
+ } catch (NoSuchConfiguredTargetException e) {
+ // All lookups should succeed because we should not be looking up any targets in error.
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+ return keys.build();
+ }
+
+ public TransitiveInfoCollection getGeneratingRule(OutputFileConfiguredTarget target) {
+ return target.getGeneratingRule();
+ }
+
+ @Override
+ public int hashCode() {
+ throw new UnsupportedOperationException(); // avoid nondeterminism
+ }
+
+ /**
+ * Return value for {@link BuildView#update} and {@code BuildTool.prepareToBuild}.
+ */
+ public static final class AnalysisResult {
+
+ public static final AnalysisResult EMPTY = new AnalysisResult(
+ ImmutableList.<ConfiguredTarget>of(), null, null, null,
+ ImmutableList.<Artifact>of(),
+ ImmutableList.<ConfiguredTarget>of(),
+ ImmutableList.<ConfiguredTarget>of(),
+ null);
+
+ private final ImmutableList<ConfiguredTarget> targetsToBuild;
+ @Nullable private final ImmutableList<ConfiguredTarget> targetsToTest;
+ @Nullable private final String error;
+ private final ActionGraph actionGraph;
+ private final ImmutableSet<Artifact> artifactsToBuild;
+ private final ImmutableSet<ConfiguredTarget> parallelTests;
+ private final ImmutableSet<ConfiguredTarget> exclusiveTests;
+ @Nullable private final TopLevelArtifactContext topLevelContext;
+
+ private AnalysisResult(
+ Collection<ConfiguredTarget> targetsToBuild, Collection<ConfiguredTarget> targetsToTest,
+ @Nullable String error, ActionGraph actionGraph,
+ Collection<Artifact> artifactsToBuild, Collection<ConfiguredTarget> parallelTests,
+ Collection<ConfiguredTarget> exclusiveTests, TopLevelArtifactContext topLevelContext) {
+ this.targetsToBuild = ImmutableList.copyOf(targetsToBuild);
+ this.targetsToTest = targetsToTest == null ? null : ImmutableList.copyOf(targetsToTest);
+ this.error = error;
+ this.actionGraph = actionGraph;
+ this.artifactsToBuild = ImmutableSet.copyOf(artifactsToBuild);
+ this.parallelTests = ImmutableSet.copyOf(parallelTests);
+ this.exclusiveTests = ImmutableSet.copyOf(exclusiveTests);
+ this.topLevelContext = topLevelContext;
+ }
+
+ /**
+ * Returns configured targets to build.
+ */
+ public Collection<ConfiguredTarget> getTargetsToBuild() {
+ return targetsToBuild;
+ }
+
+ /**
+ * Returns the configured targets to run as tests, or {@code null} if testing was not
+ * requested (e.g. "build" command rather than "test" command).
+ */
+ @Nullable
+ public Collection<ConfiguredTarget> getTargetsToTest() {
+ return targetsToTest;
+ }
+
+ public ImmutableSet<Artifact> getAdditionalArtifactsToBuild() {
+ return artifactsToBuild;
+ }
+
+ public ImmutableSet<ConfiguredTarget> getExclusiveTests() {
+ return exclusiveTests;
+ }
+
+ public ImmutableSet<ConfiguredTarget> getParallelTests() {
+ return parallelTests;
+ }
+
+ /**
+ * Returns an error description (if any).
+ */
+ @Nullable public String getError() {
+ return error;
+ }
+
+ /**
+ * Returns the action graph.
+ */
+ public ActionGraph getActionGraph() {
+ return actionGraph;
+ }
+
+ public TopLevelArtifactContext getTopLevelContext() {
+ return topLevelContext;
+ }
+ }
+
+
+ /**
+ * Returns the collection of configured targets corresponding to any of the provided targets.
+ */
+ @VisibleForTesting
+ static Iterable<? extends ConfiguredTarget> filterTestsByTargets(
+ Collection<? extends ConfiguredTarget> targets,
+ final Set<? extends Target> allowedTargets) {
+ return Iterables.filter(targets,
+ new Predicate<ConfiguredTarget>() {
+ @Override
+ public boolean apply(ConfiguredTarget rule) {
+ return allowedTargets.contains(rule.getTarget());
+ }
+ });
+ }
+
+ private void prepareToBuild(PackageRootResolver resolver) throws ViewCreationFailedException {
+ for (BuildConfiguration config : configurations.getTargetConfigurations()) {
+ config.prepareToBuild(directories.getExecRoot(), getArtifactFactory(), resolver);
+ }
+ }
+
+ @ThreadCompatible
+ public AnalysisResult update(LoadingResult loadingResult,
+ BuildConfigurationCollection configurations, Options viewOptions,
+ TopLevelArtifactContext topLevelOptions, EventHandler eventHandler, EventBus eventBus)
+ throws ViewCreationFailedException, InterruptedException {
+
+ // Detect errors during analysis and don't attempt a build.
+ //
+ // (Errors reported during the previous step, package loading, that do
+ // not cause the visitation of the transitive closure to abort, are
+ // recoverable. For example, an error encountered while evaluating an
+ // irrelevant rule in a visited package causes an error to be reported,
+ // but visitation still succeeds.)
+ ErrorCollector errorCollector = null;
+ if (!viewOptions.keepGoing) {
+ eventHandler = errorCollector = new ErrorCollector(eventHandler);
+ }
+
+ // Treat analysis warnings as errors, to enable strict builds.
+ //
+ // Warnings reported during analysis are converted to errors, ultimately
+ // triggering failure. This check needs to be added after the keep-going check
+ // above so that it is invoked first (FIFO eventHandler chain). This way, detected
+ // warnings are converted to errors first, and then the proper error handling
+ // logic is invoked.
+ WarningsAsErrorsEventHandler warningsHandler = null;
+ if (viewOptions.analysisWarningsAsErrors) {
+ eventHandler = warningsHandler = new WarningsAsErrorsEventHandler(eventHandler);
+ }
+
+ skyframeBuildView.setWarningListener(eventHandler);
+ skyframeExecutor.setErrorEventListener(eventHandler);
+
+ LOG.info("Starting analysis");
+ pollInterruptedStatus();
+
+ skyframeBuildView.resetEvaluatedConfiguredTargetKeysSet();
+
+ Collection<Target> targets = loadingResult.getTargets();
+ eventBus.post(new AnalysisPhaseStartedEvent(targets));
+
+ skyframeCacheWasInvalidated = false;
+ // Clear all cached ConfiguredTargets on configuration change. We need to do this explicitly
+ // because we need to make sure that the legacy action graph does not contain multiple actions
+ // with different versions of the same (target/host/etc.) configuration.
+ // In the future the action graph will be probably be keyed by configurations, which should
+ // obviate the need for this workaround.
+ //
+ // Also if --discard_analysis_cache was used in the last build we want to clear the legacy
+ // data.
+ if ((this.configurations != null && !configurations.equals(this.configurations))
+ || skyframeAnalysisWasDiscarded) {
+ skyframeExecutor.dropConfiguredTargets();
+ skyframeCacheWasInvalidated = true;
+ clear();
+ }
+ skyframeAnalysisWasDiscarded = false;
+ ImmutableMap<PackageIdentifier, Path> packageRoots = loadingResult.getPackageRoots();
+
+ if (buildHasIncompatiblePackageRoots(packageRoots)) {
+ // When a package root changes source artifacts with the new root will be created, but we
+ // cannot be sure that there are no references remaining to the corresponding artifacts
+ // with the old root. To avoid that scenario, the analysis cache is simply dropped when
+ // a package root change is detected.
+ LOG.info("Discarding analysis cache: package roots have changed.");
+
+ skyframeExecutor.dropConfiguredTargets();
+ skyframeCacheWasInvalidated = true;
+ clear();
+ }
+ cumulativePackageRoots.putAll(packageRoots);
+ this.configurations = configurations;
+ setArtifactRoots(packageRoots);
+
+ // Determine the configurations.
+ List<TargetAndConfiguration> nodes = nodesForTargets(targets);
+
+ List<ConfiguredTargetKey> targetSpecs =
+ Lists.transform(nodes, new Function<TargetAndConfiguration, ConfiguredTargetKey>() {
+ @Override
+ public ConfiguredTargetKey apply(TargetAndConfiguration node) {
+ return new ConfiguredTargetKey(node.getLabel(), node.getConfiguration());
+ }
+ });
+
+ prepareToBuild(new SkyframePackageRootResolver(skyframeExecutor));
+ skyframeBuildView.setWarningListener(warningsHandler);
+ skyframeExecutor.injectWorkspaceStatusData();
+ Collection<ConfiguredTarget> configuredTargets;
+ try {
+ configuredTargets = skyframeBuildView.configureTargets(
+ targetSpecs, eventBus, viewOptions.keepGoing);
+ } finally {
+ skyframeBuildView.clearInvalidatedConfiguredTargets();
+ }
+
+ int numTargetsToAnalyze = nodes.size();
+ int numSuccessful = configuredTargets.size();
+ boolean analysisSuccessful = (numSuccessful == numTargetsToAnalyze);
+ if (0 < numSuccessful && numSuccessful < numTargetsToAnalyze) {
+ String msg = String.format("Analysis succeeded for only %d of %d top-level targets",
+ numSuccessful, numTargetsToAnalyze);
+ eventHandler.handle(Event.info(msg));
+ LOG.info(msg);
+ }
+
+ postUpdateValidation(errorCollector, warningsHandler);
+
+ AnalysisResult result = createResult(loadingResult, topLevelOptions,
+ viewOptions, configuredTargets, analysisSuccessful);
+ LOG.info("Finished analysis");
+ return result;
+ }
+
+ // Validates that the update has been done correctly
+ private void postUpdateValidation(ErrorCollector errorCollector,
+ WarningsAsErrorsEventHandler warningsHandler) throws ViewCreationFailedException {
+ if (warningsHandler != null && warningsHandler.warningsEncountered()) {
+ throw new ViewCreationFailedException("Warnings being treated as errors");
+ }
+
+ if (errorCollector != null && !errorCollector.getEvents().isEmpty()) {
+ // This assertion ensures that if any errors were reported during the
+ // initialization phase, the call to configureTargets will fail with a
+ // ViewCreationFailedException. Violation of this invariant leads to
+ // incorrect builds, because the fact that errors were encountered is not
+ // properly recorded in the view (i.e. the graph of configured targets).
+ // Rule errors must be reported via RuleConfiguredTarget.reportError,
+ // which causes the rule's hasErrors() flag to be set, and thus the
+ // hasErrors() flag of anything that depends on it transitively. If the
+ // toplevel rule hasErrors, then analysis is aborted and we do not
+ // proceed to the execution phase of a build.
+ //
+ // Reporting errors directly through the Reporter does not set the error
+ // flag, so analysis may succeed spuriously, allowing the execution
+ // phase to begin with unpredictable consequences.
+ //
+ // The use of errorCollector (rather than an ErrorSensor) makes the
+ // assertion failure messages more informative.
+ // Note we tolerate errors iff --keep-going, because some of the
+ // requested targets may have had problems during analysis, but that's ok.
+ StringBuilder message = new StringBuilder("Unexpected errors reported during analysis:");
+ for (Event event : errorCollector.getEvents()) {
+ message.append('\n').append(event);
+ }
+ throw new IllegalStateException(message.toString());
+ }
+ }
+
+ /**
+ * Skyframe implementation of {@link PackageRootResolver}.
+ *
+ * <p> Note: you should not use this class inside of any SkyFunction.
+ */
+ @VisibleForTesting
+ public static final class SkyframePackageRootResolver implements PackageRootResolver {
+ private final SkyframeExecutor executor;
+
+ public SkyframePackageRootResolver(SkyframeExecutor executor) {
+ this.executor = executor;
+ }
+
+ @Override
+ public Map<PathFragment, Root> findPackageRoots(Iterable<PathFragment> execPaths) {
+ return executor.getArtifactRoots(execPaths);
+ }
+ }
+
+ private AnalysisResult createResult(LoadingResult loadingResult,
+ TopLevelArtifactContext topLevelOptions, BuildView.Options viewOptions,
+ Collection<ConfiguredTarget> configuredTargets, boolean analysisSuccessful)
+ throws InterruptedException {
+ Collection<Target> testsToRun = loadingResult.getTestsToRun();
+ Collection<ConfiguredTarget> allTargetsToTest = null;
+ if (testsToRun != null) {
+ // Determine the subset of configured targets that are meant to be run as tests.
+ allTargetsToTest = Lists.newArrayList(
+ filterTestsByTargets(configuredTargets, Sets.newHashSet(testsToRun)));
+ }
+
+ skyframeExecutor.injectTopLevelContext(topLevelOptions);
+
+ Set<Artifact> artifactsToBuild = new HashSet<>();
+ Set<ConfiguredTarget> parallelTests = new HashSet<>();
+ Set<ConfiguredTarget> exclusiveTests = new HashSet<>();
+ Collection<Artifact> buildInfoArtifacts;
+ buildInfoArtifacts = skyframeExecutor.getWorkspaceStatusArtifacts();
+ // build-info and build-changelist.
+ Preconditions.checkState(buildInfoArtifacts.size() == 2, buildInfoArtifacts);
+ artifactsToBuild.addAll(buildInfoArtifacts);
+ addExtraActionsIfRequested(viewOptions, artifactsToBuild, configuredTargets);
+ if (coverageReportActionFactory != null) {
+ Action action = coverageReportActionFactory.createCoverageReportAction(
+ allTargetsToTest,
+ getBaselineCoverageArtifacts(configuredTargets),
+ artifactFactory,
+ CoverageReportValue.ARTIFACT_OWNER);
+ if (action != null) {
+ skyframeExecutor.injectCoverageReportData(action);
+ artifactsToBuild.addAll(action.getOutputs());
+ }
+ }
+
+ // Note that this must come last, so that the tests are scheduled after all artifacts are built.
+ scheduleTestsIfRequested(parallelTests, exclusiveTests, topLevelOptions, allTargetsToTest);
+
+ String error = !loadingResult.hasLoadingError()
+ ? (analysisSuccessful
+ ? null
+ : "execution phase succeeded, but not all targets were analyzed")
+ : "execution phase succeeded, but there were loading phase errors";
+ return new AnalysisResult(configuredTargets, allTargetsToTest, error, getActionGraph(),
+ artifactsToBuild, parallelTests, exclusiveTests, topLevelOptions);
+ }
+
+ private static ImmutableSet<Artifact> getBaselineCoverageArtifacts(
+ Collection<ConfiguredTarget> configuredTargets) {
+ Set<Artifact> baselineCoverageArtifacts = Sets.newHashSet();
+ for (ConfiguredTarget target : configuredTargets) {
+ BaselineCoverageArtifactsProvider provider =
+ target.getProvider(BaselineCoverageArtifactsProvider.class);
+ if (provider != null) {
+ baselineCoverageArtifacts.addAll(provider.getBaselineCoverageArtifacts());
+ }
+ }
+ return ImmutableSet.copyOf(baselineCoverageArtifacts);
+ }
+
+ private void addExtraActionsIfRequested(BuildView.Options viewOptions,
+ Set<Artifact> artifactsToBuild, Iterable<ConfiguredTarget> topLevelTargets) {
+ NestedSetBuilder<ExtraArtifactSet> builder = NestedSetBuilder.stableOrder();
+ for (ConfiguredTarget topLevel : topLevelTargets) {
+ ExtraActionArtifactsProvider provider = topLevel.getProvider(
+ ExtraActionArtifactsProvider.class);
+ if (provider != null) {
+ if (viewOptions.extraActionTopLevelOnly) {
+ builder.add(ExtraArtifactSet.of(topLevel.getLabel(), provider.getExtraActionArtifacts()));
+ } else {
+ builder.addTransitive(provider.getTransitiveExtraActionArtifacts());
+ }
+ }
+ }
+
+ RegexFilter filter = viewOptions.extraActionFilter;
+ for (ExtraArtifactSet set : builder.build()) {
+ boolean filterMatches = filter == null || filter.isIncluded(set.getLabel().toString());
+ if (filterMatches) {
+ Iterables.addAll(artifactsToBuild, set.getArtifacts());
+ }
+ }
+ }
+
+ private static void scheduleTestsIfRequested(Collection<ConfiguredTarget> targetsToTest,
+ Collection<ConfiguredTarget> targetsToTestExclusive, TopLevelArtifactContext topLevelOptions,
+ Collection<ConfiguredTarget> allTestTargets) {
+ if (!topLevelOptions.compileOnly() && !topLevelOptions.compilationPrerequisitesOnly()
+ && allTestTargets != null) {
+ scheduleTests(targetsToTest, targetsToTestExclusive, allTestTargets,
+ topLevelOptions.runTestsExclusively());
+ }
+ }
+
+
+ /**
+ * Returns set of artifacts representing test results, writing into targetsToTest and
+ * targetsToTestExclusive.
+ */
+ private static void scheduleTests(Collection<ConfiguredTarget> targetsToTest,
+ Collection<ConfiguredTarget> targetsToTestExclusive,
+ Collection<ConfiguredTarget> allTestTargets,
+ boolean isExclusive) {
+ for (ConfiguredTarget target : allTestTargets) {
+ if (target.getTarget() instanceof Rule) {
+ boolean exclusive =
+ isExclusive || TargetUtils.isExclusiveTestRule((Rule) target.getTarget());
+ Collection<ConfiguredTarget> testCollection = exclusive
+ ? targetsToTestExclusive
+ : targetsToTest;
+ testCollection.add(target);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ List<TargetAndConfiguration> nodesForTargets(Collection<Target> targets) {
+ // We use a hash set here to remove duplicate nodes; this can happen for input files and package
+ // groups.
+ LinkedHashSet<TargetAndConfiguration> nodes = new LinkedHashSet<>(targets.size());
+ for (BuildConfiguration config : configurations.getTargetConfigurations()) {
+ for (Target target : targets) {
+ nodes.add(new TargetAndConfiguration(target,
+ BuildConfigurationCollection.configureTopLevelTarget(config, target)));
+ }
+ }
+ return ImmutableList.copyOf(nodes);
+ }
+
+ /**
+ * Detects when a package root changes between instances of incremental analysis.
+ *
+ * <p>This case is currently problematic for incremental analysis because when a package root
+ * changes, source artifacts with the new root will be created, but we can not be sure that there
+ * are no references remaining to the corresponding artifacts with the old root.
+ */
+ private boolean buildHasIncompatiblePackageRoots(Map<PackageIdentifier, Path> packageRoots) {
+ for (Map.Entry<PackageIdentifier, Path> entry : packageRoots.entrySet()) {
+ Path prevRoot = cumulativePackageRoots.get(entry.getKey());
+ if (prevRoot != null && !entry.getValue().equals(prevRoot)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns an existing ConfiguredTarget for the specified target and
+ * configuration, or null if none exists. No validity check is done.
+ */
+ @ThreadSafe
+ public ConfiguredTarget getExistingConfiguredTarget(Target target, BuildConfiguration config) {
+ return getExistingConfiguredTarget(target.getLabel(), config);
+ }
+
+ /**
+ * Returns an existing ConfiguredTarget for the specified node, or null if none exists. No
+ * validity check is done.
+ */
+ @ThreadSafe
+ private ConfiguredTarget getExistingConfiguredTarget(
+ Label label, BuildConfiguration configuration) {
+ return Iterables.getFirst(
+ skyframeExecutor.getConfiguredTargets(
+ ImmutableList.of(new Dependency(label, configuration))),
+ null);
+ }
+
+ @VisibleForTesting
+ ListMultimap<Attribute, ConfiguredTarget> getPrerequisiteMapForTesting(ConfiguredTarget target) {
+ DependencyResolver resolver = new DependencyResolver() {
+ @Override
+ protected void invalidVisibilityReferenceHook(TargetAndConfiguration node, Label label) {
+ throw new RuntimeException("bad visibility on " + label + " during testing unexpected");
+ }
+
+ @Override
+ protected void invalidPackageGroupReferenceHook(TargetAndConfiguration node, Label label) {
+ throw new RuntimeException("bad package group on " + label + " during testing unexpected");
+ }
+
+ @Override
+ protected Target getTarget(Label label) throws NoSuchThingException {
+ return packageManager.getLoadedTarget(label);
+ }
+ };
+ TargetAndConfiguration ctNode = new TargetAndConfiguration(target);
+ ListMultimap<Attribute, Dependency> depNodeNames;
+ try {
+ depNodeNames = resolver.dependentNodeMap(ctNode, null, getConfigurableAttributeKeys(ctNode));
+ } catch (EvalException e) {
+ throw new IllegalStateException(e);
+ }
+
+ final Map<LabelAndConfiguration, ConfiguredTarget> depMap = new HashMap<>();
+ for (ConfiguredTarget dep : skyframeExecutor.getConfiguredTargets(depNodeNames.values())) {
+ depMap.put(LabelAndConfiguration.of(dep.getLabel(), dep.getConfiguration()), dep);
+ }
+
+ return Multimaps.transformValues(depNodeNames, new Function<Dependency, ConfiguredTarget>() {
+ @Override
+ public ConfiguredTarget apply(Dependency depName) {
+ return depMap.get(LabelAndConfiguration.of(depName.getLabel(),
+ depName.getConfiguration()));
+ }
+ });
+ }
+
+ /**
+ * Sets the possible artifact roots in the artifact factory. This allows the
+ * factory to resolve paths with unknown roots to artifacts.
+ * <p>
+ * <em>Note: This must be called before any call to
+ * {@link #getConfiguredTarget(Label, BuildConfiguration)}
+ * </em>
+ */
+ @VisibleForTesting // for BuildViewTestCase
+ void setArtifactRoots(ImmutableMap<PackageIdentifier, Path> packageRoots) {
+ Map<Path, Root> rootMap = new HashMap<>();
+ Map<PackageIdentifier, Root> realPackageRoots = new HashMap<>();
+ for (Map.Entry<PackageIdentifier, Path> entry : packageRoots.entrySet()) {
+ Root root = rootMap.get(entry.getValue());
+ if (root == null) {
+ root = Root.asSourceRoot(entry.getValue());
+ rootMap.put(entry.getValue(), root);
+ }
+ realPackageRoots.put(entry.getKey(), root);
+ }
+ // Source Artifact roots:
+ artifactFactory.setPackageRoots(realPackageRoots);
+
+ // Derived Artifact roots:
+ ImmutableList.Builder<Root> roots = ImmutableList.builder();
+
+ // build-info.txt and friends; this root is not configuration specific.
+ roots.add(directories.getBuildDataDirectory());
+
+ // The roots for each configuration - duplicates are automatically removed in the call below.
+ for (BuildConfiguration cfg : configurations.getAllConfigurations()) {
+ roots.addAll(cfg.getRoots());
+ }
+
+ artifactFactory.setDerivedArtifactRoots(roots.build());
+ }
+
+ /**
+ * Returns a configured target for the specified target and configuration.
+ * This should only be called from test cases, and is needed, because
+ * plain {@link #getConfiguredTarget(Target, BuildConfiguration)} does not
+ * construct the configured target graph, and would thus fail if called from
+ * outside an update.
+ */
+ @VisibleForTesting
+ public ConfiguredTarget getConfiguredTargetForTesting(Label label, BuildConfiguration config)
+ throws NoSuchPackageException, NoSuchTargetException {
+ return getConfiguredTargetForTesting(packageManager.getLoadedTarget(label), config);
+ }
+
+ @VisibleForTesting
+ public ConfiguredTarget getConfiguredTargetForTesting(Target target, BuildConfiguration config) {
+ return skyframeExecutor.getConfiguredTargetForTesting(target.getLabel(), config);
+ }
+
+ /**
+ * Returns a RuleContext which is the same as the original RuleContext of the target parameter.
+ */
+ @VisibleForTesting
+ public RuleContext getRuleContextForTesting(ConfiguredTarget target,
+ StoredEventHandler eventHandler) {
+ BuildConfiguration config = target.getConfiguration();
+ CachingAnalysisEnvironment analysisEnvironment =
+ new CachingAnalysisEnvironment(artifactFactory,
+ new ConfiguredTargetKey(target.getLabel(), config),
+ /*isSystemEnv=*/false, config.extendedSanityChecks(), eventHandler,
+ /*skyframeEnv=*/null, config.isActionsEnabled(), binTools);
+ RuleContext ruleContext = new RuleContext.Builder(analysisEnvironment,
+ (Rule) target.getTarget(), config, ruleClassProvider.getPrerequisiteValidator())
+ .setVisibility(NestedSetBuilder.<PackageSpecification>create(
+ Order.STABLE_ORDER, PackageSpecification.EVERYTHING))
+ .setPrerequisites(getPrerequisiteMapForTesting(target))
+ .setConfigConditions(ImmutableSet.<ConfigMatchingProvider>of())
+ .build();
+ return ruleContext;
+ }
+
+ /**
+ * Tests and clears the current thread's pending "interrupted" status, and
+ * throws InterruptedException iff it was set.
+ */
+ protected final void pollInterruptedStatus() throws InterruptedException {
+ if (Thread.interrupted()) {
+ throw new InterruptedException();
+ }
+ }
+
+ /**
+ * Drops the analysis cache. If building with Skyframe, targets in {@code topLevelTargets} may
+ * remain in the cache for use during the execution phase.
+ *
+ * @see BuildView.Options#discardAnalysisCache
+ */
+ public void clearAnalysisCache(Collection<ConfiguredTarget> topLevelTargets) {
+ // TODO(bazel-team): Consider clearing packages too to save more memory.
+ skyframeAnalysisWasDiscarded = true;
+ skyframeExecutor.clearAnalysisCache(topLevelTargets);
+ }
+
+ /********************************************************************
+ * *
+ * 'blaze dump' related functions *
+ * *
+ ********************************************************************/
+
+ /**
+ * Collects and stores error events while also forwarding them to another eventHandler.
+ */
+ public static class ErrorCollector extends DelegatingEventHandler {
+ private final List<Event> events;
+
+ public ErrorCollector(EventHandler delegate) {
+ super(delegate);
+ this.events = Lists.newArrayList();
+ }
+
+ public List<Event> getEvents() {
+ return events;
+ }
+
+ @Override
+ public void handle(Event e) {
+ super.handle(e);
+ if (e.getKind() == EventKind.ERROR) {
+ events.add(e);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java b/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java
new file mode 100644
index 0000000..bc45ba3
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java
@@ -0,0 +1,303 @@
+// 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.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ArtifactFactory;
+import com.google.devtools.build.lib.actions.ArtifactOwner;
+import com.google.devtools.build.lib.actions.MiddlemanFactory;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoCollection;
+import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory.BuildInfoKey;
+import com.google.devtools.build.lib.analysis.config.BinTools;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.events.StoredEventHandler;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.skyframe.BuildInfoCollectionValue;
+import com.google.devtools.build.lib.skyframe.WorkspaceStatusValue;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.skyframe.SkyFunction;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import javax.annotation.Nullable;
+
+/**
+ * The implementation of AnalysisEnvironment used for analysis. It tracks metadata for each
+ * configured target, such as the errors and warnings emitted by that target. It is intended that
+ * a separate instance is used for each configured target, so that these don't mix up.
+ */
+public class CachingAnalysisEnvironment implements AnalysisEnvironment {
+ private final ArtifactFactory artifactFactory;
+
+ private final ArtifactOwner owner;
+ /**
+ * If this is the system analysis environment, then errors and warnings are directly reported
+ * to the global reporter, rather than stored, i.e., we don't track here whether there are any
+ * errors.
+ */
+ private final boolean isSystemEnv;
+ private final boolean extendedSanityChecks;
+
+ /**
+ * If false, no actions will be registered, they'll all be just dropped.
+ *
+ * <p>Usually, an analysis environment should register all actions. However, in some scenarios we
+ * analyze some targets twice, but the first one only serves the purpose of collecting information
+ * for the second analysis. In this case we don't register actions created by the first pass in
+ * order to avoid action conflicts.
+ */
+ private final boolean allowRegisteringActions;
+
+ private boolean enabled = true;
+ private MiddlemanFactory middlemanFactory;
+ private EventHandler errorEventListener;
+ private SkyFunction.Environment skyframeEnv;
+ private Map<Artifact, String> artifacts;
+ private final BinTools binTools;
+
+ /**
+ * The list of actions registered by the configured target this analysis environment is
+ * responsible for. May get cleared out at the end of the analysis of said target.
+ */
+ final List<Action> actions = new ArrayList<>();
+
+ public CachingAnalysisEnvironment(ArtifactFactory artifactFactory,
+ ArtifactOwner owner, boolean isSystemEnv, boolean extendedSanityChecks,
+ EventHandler errorEventListener, SkyFunction.Environment env, boolean allowRegisteringActions,
+ BinTools binTools) {
+ this.artifactFactory = artifactFactory;
+ this.owner = Preconditions.checkNotNull(owner);
+ this.isSystemEnv = isSystemEnv;
+ this.extendedSanityChecks = extendedSanityChecks;
+ this.errorEventListener = errorEventListener;
+ this.skyframeEnv = env;
+ this.allowRegisteringActions = allowRegisteringActions;
+ this.binTools = binTools;
+ middlemanFactory = new MiddlemanFactory(artifactFactory, this);
+ artifacts = new HashMap<>();
+ }
+
+ public void disable(Target target) {
+ if (!hasErrors() && allowRegisteringActions) {
+ verifyGeneratedArtifactHaveActions(target);
+ }
+ artifacts = null;
+ middlemanFactory = null;
+ enabled = false;
+ errorEventListener = null;
+ skyframeEnv = null;
+ }
+
+ private static StringBuilder shortDescription(Action action) {
+ if (action == null) {
+ return new StringBuilder("null Action");
+ }
+ return new StringBuilder()
+ .append(action.getClass().getName())
+ .append(' ')
+ .append(action.getMnemonic());
+ }
+
+ /**
+ * Sanity checks that all generated artifacts have a generating action.
+ * @param target for error reporting
+ */
+ public void verifyGeneratedArtifactHaveActions(Target target) {
+ Collection<String> orphanArtifacts = getOrphanArtifactMap().values();
+ List<String> checkedActions = null;
+ if (!orphanArtifacts.isEmpty()) {
+ checkedActions = Lists.newArrayListWithCapacity(actions.size());
+ for (Action action : actions) {
+ StringBuilder sb = shortDescription(action);
+ for (Artifact o : action.getOutputs()) {
+ sb.append("\n ");
+ sb.append(o.getExecPathString());
+ }
+ checkedActions.add(sb.toString());
+ }
+ throw new IllegalStateException(
+ String.format(
+ "%s %s : These artifacts miss a generating action:\n%s\n"
+ + "These actions we checked:\n%s\n",
+ target.getTargetKind(), target.getLabel(),
+ Joiner.on('\n').join(orphanArtifacts), Joiner.on('\n').join(checkedActions)));
+ }
+ }
+
+ @Override
+ public ImmutableSet<Artifact> getOrphanArtifacts() {
+ return ImmutableSet.copyOf(getOrphanArtifactMap().keySet());
+ }
+
+ private Map<Artifact, String> getOrphanArtifactMap() {
+ // Construct this set to avoid poor performance under large --runs_per_test.
+ Set<Artifact> artifactsWithActions = new HashSet<>();
+ for (Action action : actions) {
+ // Don't bother checking that every Artifact only appears once; that test is performed
+ // elsewhere (see #testNonUniqueOutputs in ActionListenerIntegrationTest).
+ artifactsWithActions.addAll(action.getOutputs());
+ }
+ // The order of the artifacts.entrySet iteration is unspecified - we use a TreeMap here to
+ // guarantee that the return value of this method is deterministic.
+ Map<Artifact, String> orphanArtifacts = new TreeMap<>();
+ for (Map.Entry<Artifact, String> entry : artifacts.entrySet()) {
+ Artifact a = entry.getKey();
+ if (!a.isSourceArtifact() && !artifactsWithActions.contains(a)) {
+ orphanArtifacts.put(a, String.format("%s\n%s",
+ a.getExecPathString(), // uncovered artifact
+ entry.getValue())); // origin of creation
+ }
+ }
+ return orphanArtifacts;
+ }
+
+ @Override
+ public EventHandler getEventHandler() {
+ return errorEventListener;
+ }
+
+ @Override
+ public boolean hasErrors() {
+ // The system analysis environment never has errors.
+ if (isSystemEnv) {
+ return false;
+ }
+ Preconditions.checkState(enabled);
+ return ((StoredEventHandler) errorEventListener).hasErrors();
+ }
+
+ @Override
+ public MiddlemanFactory getMiddlemanFactory() {
+ Preconditions.checkState(enabled);
+ return middlemanFactory;
+ }
+
+ /**
+ * Keeps track of artifacts. We check that all of them have an owner when the environment is
+ * sealed (disable()). For performance reasons we only track the originating stacktrace when
+ * running with --experimental_extended_sanity_checks.
+ */
+ private Artifact trackArtifactAndOrigin(Artifact a, @Nullable Throwable e) {
+ if ((e != null) && !artifacts.containsKey(a)) {
+ StringWriter sw = new StringWriter();
+ e.printStackTrace(new PrintWriter(sw));
+ artifacts.put(a, sw.toString());
+ } else {
+ artifacts.put(a, "No origin, run with --experimental_extended_sanity_checks");
+ }
+ return a;
+ }
+
+ @Override
+ public Artifact getDerivedArtifact(PathFragment rootRelativePath, Root root) {
+ Preconditions.checkState(enabled);
+ return trackArtifactAndOrigin(
+ artifactFactory.getDerivedArtifact(rootRelativePath, root, getOwner()),
+ extendedSanityChecks ? new Throwable() : null);
+ }
+
+ @Override
+ public Artifact getFilesetArtifact(PathFragment rootRelativePath, Root root) {
+ Preconditions.checkState(enabled);
+ return trackArtifactAndOrigin(
+ artifactFactory.getFilesetArtifact(rootRelativePath, root, getOwner()),
+ extendedSanityChecks ? new Throwable() : null);
+ }
+
+ @Override
+ public Artifact getConstantMetadataArtifact(PathFragment rootRelativePath, Root root) {
+ return artifactFactory.getConstantMetadataArtifact(rootRelativePath, root, getOwner());
+ }
+
+ @Override
+ public Artifact getEmbeddedToolArtifact(String embeddedPath) {
+ Preconditions.checkState(enabled);
+ return binTools.getEmbeddedArtifact(embeddedPath, artifactFactory);
+ }
+
+ @Override
+ public void registerAction(Action... actions) {
+ Preconditions.checkState(enabled);
+ if (allowRegisteringActions) {
+ for (Action action : actions) {
+ this.actions.add(action);
+ }
+ }
+ }
+
+ @Override
+ public Action getLocalGeneratingAction(Artifact artifact) {
+ Preconditions.checkState(allowRegisteringActions);
+ for (Action action : actions) {
+ if (action.getOutputs().contains(artifact)) {
+ return action;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Collection<Action> getRegisteredActions() {
+ return Collections.unmodifiableCollection(actions);
+ }
+
+ @Override
+ public SkyFunction.Environment getSkyframeEnv() {
+ return skyframeEnv;
+ }
+
+ @Override
+ public Artifact getStableWorkspaceStatusArtifact() {
+ return ((WorkspaceStatusValue) skyframeEnv.getValue(WorkspaceStatusValue.SKY_KEY))
+ .getStableArtifact();
+ }
+
+ @Override
+ public Artifact getVolatileWorkspaceStatusArtifact() {
+ return ((WorkspaceStatusValue) skyframeEnv.getValue(WorkspaceStatusValue.SKY_KEY))
+ .getVolatileArtifact();
+ }
+
+ @Override
+ public ImmutableList<Artifact> getBuildInfo(RuleContext ruleContext, BuildInfoKey key) {
+ boolean stamp = AnalysisUtils.isStampingEnabled(ruleContext);
+ BuildInfoCollection collection =
+ ((BuildInfoCollectionValue) skyframeEnv.getValue(BuildInfoCollectionValue.key(
+ new BuildInfoCollectionValue.BuildInfoKeyAndConfig(key, ruleContext.getConfiguration()))))
+ .getCollection();
+ return stamp ? collection.getStampedBuildInfo() : collection.getRedactedBuildInfo();
+ }
+
+ @Override
+ public ArtifactOwner getOwner() {
+ return owner;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/CommandHelper.java b/src/main/java/com/google/devtools/build/lib/analysis/CommandHelper.java
new file mode 100644
index 0000000..2c05f0f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/CommandHelper.java
@@ -0,0 +1,307 @@
+// 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.annotations.VisibleForTesting;
+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.Sets;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.BaseSpawn;
+import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.SkylarkCallable;
+import com.google.devtools.build.lib.syntax.SkylarkModule;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Provides shared functionality for parameterized command-line launching
+ * e.g. {@link com.google.devtools.build.lib.view.genrule.GenRule}
+ * Also used by {@link com.google.devtools.build.lib.rules.extra.ExtraActionFactory}.
+ *
+ * Two largely independent separate sets of functionality are provided:
+ * 1- string interpolation for {@code $(location[s] ...)} and {@code $(MakeVariable)}
+ * 2- a utility to build potentially large command lines (presumably made of multiple commands),
+ * that if presumed too large for the kernel's taste can be dumped into a shell script
+ * that will contain the same commands,
+ * at which point the shell script is added to the list of inputs.
+ */
+@SkylarkModule(name = "command_helper", doc = "A helper class to create shell commands.")
+public final class CommandHelper {
+
+ /**
+ * Maximum total command-line length, in bytes, not counting "/bin/bash -c ".
+ * If the command is very long, then we write the command to a script file,
+ * to avoid overflowing any limits on command-line length.
+ * For short commands, we just use /bin/bash -c command.
+ */
+ @VisibleForTesting
+ public static int maxCommandLength = 64000;
+
+ /**
+ * A map of remote path prefixes and corresponding runfiles manifests for tools
+ * used by this rule.
+ */
+ private final ImmutableMap<PathFragment, Artifact> remoteRunfileManifestMap;
+
+ /**
+ * Use labelMap for heuristically expanding labels (does not include "outs")
+ * This is similar to heuristic location expansion in LocationExpander
+ * and should be kept in sync.
+ */
+ private final ImmutableMap<Label, ImmutableCollection<Artifact>> labelMap;
+
+ /**
+ * The ruleContext this helper works on
+ */
+ private final RuleContext ruleContext;
+
+ /**
+ * Output executable files from the 'tools' attribute.
+ */
+ private final ImmutableList<Artifact> resolvedTools;
+
+ /**
+ * Creates an {@link CommandHelper}.
+ *
+ * @param tools - Resolves set of tools into set of executable binaries. Populates manifests,
+ * remoteRunfiles and label map where required.
+ * @param labelMap - Adds files to set of known files of label. Used for resolving $(location)
+ * variables.
+ */
+ public CommandHelper(RuleContext ruleContext,
+ Iterable<FilesToRunProvider> tools,
+ ImmutableMap<Label, Iterable<Artifact>> labelMap) {
+ this.ruleContext = ruleContext;
+
+ ImmutableList.Builder<Artifact> resolvedToolsBuilder = ImmutableList.builder();
+ ImmutableMap.Builder<PathFragment, Artifact> remoteRunfileManifestBuilder =
+ ImmutableMap.builder();
+ Map<Label, Collection<Artifact>> tempLabelMap = new HashMap<>();
+
+ for (Map.Entry<Label, Iterable<Artifact>> entry : labelMap.entrySet()) {
+ Iterables.addAll(mapGet(tempLabelMap, entry.getKey()), entry.getValue());
+ }
+
+ for (FilesToRunProvider tool : tools) { // (Note: host configuration)
+ Label label = tool.getLabel();
+ Collection<Artifact> files = tool.getFilesToRun();
+ resolvedToolsBuilder.addAll(files);
+ Artifact executableArtifact = tool.getExecutable();
+ // If the label has an executable artifact add that to the multimaps.
+ if (executableArtifact != null) {
+ mapGet(tempLabelMap, label).add(executableArtifact);
+ // Also send the runfiles when running remotely.
+ Artifact runfilesManifest = tool.getRunfilesManifest();
+ if (runfilesManifest != null) {
+ remoteRunfileManifestBuilder.put(
+ BaseSpawn.runfilesForFragment(executableArtifact.getExecPath()), runfilesManifest);
+ }
+ } else {
+ // Map all depArtifacts to the respective label using the multimaps.
+ Iterables.addAll(mapGet(tempLabelMap, label), files);
+ }
+ }
+
+ this.resolvedTools = resolvedToolsBuilder.build();
+ this.remoteRunfileManifestMap = remoteRunfileManifestBuilder.build();
+ ImmutableMap.Builder<Label, ImmutableCollection<Artifact>> labelMapBuilder =
+ ImmutableMap.builder();
+ for (Entry<Label, Collection<Artifact>> entry : tempLabelMap.entrySet()) {
+ labelMapBuilder.put(entry.getKey(), ImmutableList.copyOf(entry.getValue()));
+ }
+ this.labelMap = labelMapBuilder.build();
+ }
+
+ @SkylarkCallable(name = "resolved_tools", doc = "", structField = true)
+ public List<Artifact> getResolvedTools() {
+ return resolvedTools;
+ }
+
+ @SkylarkCallable(name = "runfiles_manifests", doc = "", structField = true)
+ public ImmutableMap<PathFragment, Artifact> getRemoteRunfileManifestMap() {
+ return remoteRunfileManifestMap;
+ }
+
+ // Returns the value in the specified corresponding to 'key', creating and
+ // inserting an empty container if absent. We use Map not Multimap because
+ // we need to distinguish the cases of "empty value" and "absent key".
+ private static Collection<Artifact> mapGet(Map<Label, Collection<Artifact>> map, Label key) {
+ Collection<Artifact> values = map.get(key);
+ if (values == null) {
+ // We use sets not lists, because it's conceivable that the same artifact
+ // could appear twice, e.g. in "srcs" and "deps".
+ values = Sets.newHashSet();
+ map.put(key, values);
+ }
+ return values;
+ }
+
+ /**
+ * Resolves the 'cmd' attribute, and expands known locations for $(location)
+ * variables.
+ */
+ @SkylarkCallable(doc = "")
+ public String resolveCommandAndExpandLabels(Boolean supportLegacyExpansion,
+ Boolean allowDataInLabel) {
+ String command = ruleContext.attributes().get("cmd", Type.STRING);
+ command = new LocationExpander(ruleContext, allowDataInLabel).expand("cmd", command);
+
+ if (supportLegacyExpansion) {
+ command = expandLabels(command, labelMap);
+ }
+ return command;
+ }
+
+ /**
+ * Expands labels occurring in the string "expr" in the rule 'cmd'.
+ * Each label must be valid, be a declared prerequisite, and expand to a
+ * unique path.
+ *
+ * <p>If the expansion fails, an attribute error is reported and the original
+ * expression is returned.
+ */
+ private <T extends Iterable<Artifact>> String expandLabels(String expr, Map<Label, T> labelMap) {
+ try {
+ return LabelExpander.expand(expr, labelMap, ruleContext.getLabel());
+ } catch (LabelExpander.NotUniqueExpansionException nuee) {
+ ruleContext.attributeError("cmd", nuee.getMessage());
+ return expr;
+ }
+ }
+
+ private static Pair<List<String>, Artifact> buildCommandLineMaybeWithScriptFile(
+ RuleContext ruleContext, String command, String scriptPostFix) {
+ List<String> argv;
+ Artifact scriptFileArtifact = null;
+ if (command.length() <= maxCommandLength) {
+ argv = buildCommandLineSimpleArgv(ruleContext, command);
+ } else {
+ // Use script file.
+ scriptFileArtifact = buildCommandLineArtifact(ruleContext, command, scriptPostFix);
+ argv = buildCommandLineArgvWithArtifact(ruleContext, scriptFileArtifact);
+ }
+ return Pair.of(argv, scriptFileArtifact);
+
+ }
+
+ /**
+ * Builds the set of command-line arguments. Creates a bash script if the
+ * command line is longer than the allowed maximum {@link
+ * #maxCommandLength}. Fixes up the input artifact list with the
+ * created bash script when required.
+ * TODO(bazel-team): do away with the side effect on inputs (ugh).
+ */
+ public static List<String> buildCommandLine(RuleContext ruleContext,
+ String command, NestedSetBuilder<Artifact> inputs, String scriptPostFix) {
+ Pair<List<String>, Artifact> argvAndScriptFile =
+ buildCommandLineMaybeWithScriptFile(ruleContext, command, scriptPostFix);
+ if (argvAndScriptFile.second != null) {
+ inputs.add(argvAndScriptFile.second);
+ }
+ return argvAndScriptFile.first;
+ }
+
+ /**
+ * Builds the set of command-line arguments. Creates a bash script if the
+ * command line is longer than the allowed maximum {@link
+ * #maxCommandLength}. Fixes up the input artifact list with the
+ * created bash script when required.
+ * TODO(bazel-team): do away with the side effect on inputs (ugh).
+ */
+ public static List<String> buildCommandLine(RuleContext ruleContext,
+ String command, List<Artifact> inputs, String scriptPostFix) {
+ Pair<List<String>, Artifact> argvAndScriptFile =
+ buildCommandLineMaybeWithScriptFile(ruleContext, command, scriptPostFix);
+ if (argvAndScriptFile.second != null) {
+ inputs.add(argvAndScriptFile.second);
+ }
+ return argvAndScriptFile.first;
+ }
+
+ /**
+ * Builds the set of command-line arguments. Creates a bash script if the
+ * command line is longer than the allowed maximum {@link
+ * #maxCommandLength}. Fixes up the input artifact list with the
+ * created bash script when required.
+ * TODO(bazel-team): do away with the side effect on inputs (ugh).
+ */
+ public static List<String> buildCommandLine(RuleContext ruleContext,
+ String command, ImmutableSet.Builder<Artifact> inputs, String scriptPostFix) {
+ Pair<List<String>, Artifact> argvAndScriptFile =
+ buildCommandLineMaybeWithScriptFile(ruleContext, command, scriptPostFix);
+ if (argvAndScriptFile.second != null) {
+ inputs.add(argvAndScriptFile.second);
+ }
+ return argvAndScriptFile.first;
+ }
+
+ private static ImmutableList<String> buildCommandLineArgvWithArtifact(RuleContext ruleContext,
+ Artifact scriptFileArtifact) {
+ return ImmutableList.of(
+ ruleContext.getConfiguration().getShExecutable().getPathString(),
+ scriptFileArtifact.getExecPathString());
+ }
+
+ private static Artifact buildCommandLineArtifact(RuleContext ruleContext, String command,
+ String scriptPostFix) {
+ String scriptFileName = ruleContext.getTarget().getName() + scriptPostFix;
+ String scriptFileContents = "#!/bin/bash\n" + command;
+ Artifact scriptFileArtifact = FileWriteAction.createFile(
+ ruleContext, scriptFileName, scriptFileContents, /*executable=*/true);
+ return scriptFileArtifact;
+ }
+
+ private static ImmutableList<String> buildCommandLineSimpleArgv(RuleContext ruleContext,
+ String command) {
+ return ImmutableList.of(
+ ruleContext.getConfiguration().getShExecutable().getPathString(), "-c", command);
+ }
+
+ /**
+ * Builds the set of command-line arguments. Creates a bash script if the
+ * command line is longer than the allowed maximum {@link
+ * #maxCommandLength}. Fixes up the input artifact list with the
+ * created bash script when required.
+ */
+ public List<String> buildCommandLine(
+ String command, NestedSetBuilder<Artifact> inputs, String scriptPostFix) {
+ return buildCommandLine(ruleContext, command, inputs, scriptPostFix);
+ }
+
+ /**
+ * Builds the set of command-line arguments. Creates a bash script if the
+ * command line is longer than the allowed maximum {@link
+ * #maxCommandLength}. Fixes up the input artifact list with the
+ * created bash script when required.
+ */
+ @SkylarkCallable(doc = "")
+ public List<String> buildCommandLine(
+ String command, List<Artifact> inputs, String scriptPostFix) {
+ return buildCommandLine(ruleContext, command, inputs, scriptPostFix);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/CompilationHelper.java b/src/main/java/com/google/devtools/build/lib/analysis/CompilationHelper.java
new file mode 100644
index 0000000..23dd8d4
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/CompilationHelper.java
@@ -0,0 +1,92 @@
+// 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.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.MiddlemanFactory;
+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 java.util.List;
+
+/**
+ * A helper class for compilation helpers.
+ */
+public final class CompilationHelper {
+ /**
+ * Returns a middleman for all files to build for the given configured target.
+ * If multiple calls are made, then it returns the same artifact for
+ * configurations with the same internal directory.
+ *
+ * <p>The resulting middleman only aggregates the inputs and must be expanded
+ * before an action using it can be sent to a distributed execution-system.
+ */
+ public static NestedSet<Artifact> getAggregatingMiddleman(
+ RuleContext ruleContext, String purpose, NestedSet<Artifact> filesToBuild) {
+ return NestedSetBuilder.wrap(Order.STABLE_ORDER, getMiddlemanInternal(
+ ruleContext.getAnalysisEnvironment(), ruleContext, ruleContext.getActionOwner(), purpose,
+ filesToBuild));
+ }
+
+ /**
+ * Internal implementation for getAggregatingMiddleman / getAggregatingMiddlemanWithSolibSymlinks.
+ */
+ private static List<Artifact> getMiddlemanInternal(AnalysisEnvironment env,
+ RuleContext ruleContext, ActionOwner actionOwner, String purpose,
+ NestedSet<Artifact> filesToBuild) {
+ if (filesToBuild == null) {
+ return ImmutableList.of();
+ }
+ MiddlemanFactory factory = env.getMiddlemanFactory();
+ return ImmutableList.of(factory.createMiddlemanAllowMultiple(
+ env, actionOwner, purpose, filesToBuild,
+ ruleContext.getConfiguration().getMiddlemanDirectory()));
+ }
+
+ // TODO(bazel-team): remove this duplicated code after the ConfiguredTarget migration
+ /**
+ * Returns a middleman for all files to build for the given configured target.
+ * If multiple calls are made, then it returns the same artifact for
+ * configurations with the same internal directory.
+ *
+ * <p>The resulting middleman only aggregates the inputs and must be expanded
+ * before an action using it can be sent to a distributed execution-system.
+ */
+ public static NestedSet<Artifact> getAggregatingMiddleman(
+ RuleContext ruleContext, String purpose, TransitiveInfoCollection dep) {
+ return NestedSetBuilder.wrap(Order.STABLE_ORDER, getMiddlemanInternal(
+ ruleContext.getAnalysisEnvironment(), ruleContext, ruleContext.getActionOwner(), purpose,
+ dep));
+ }
+
+ /**
+ * Internal implementation for getAggregatingMiddleman / getAggregatingMiddlemanWithSolibSymlinks.
+ */
+ private static List<Artifact> getMiddlemanInternal(AnalysisEnvironment env,
+ RuleContext ruleContext, ActionOwner actionOwner, String purpose,
+ TransitiveInfoCollection dep) {
+ if (dep == null) {
+ return ImmutableList.of();
+ }
+ MiddlemanFactory factory = env.getMiddlemanFactory();
+ Iterable<Artifact> artifacts = dep.getProvider(FileProvider.class).getFilesToBuild();
+ return ImmutableList.of(factory.createMiddlemanAllowMultiple(
+ env, actionOwner, purpose, artifacts,
+ ruleContext.getConfiguration().getMiddlemanDirectory()));
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/CompilationPrerequisitesProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/CompilationPrerequisitesProvider.java
new file mode 100644
index 0000000..8c3a6ce
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/CompilationPrerequisitesProvider.java
@@ -0,0 +1,38 @@
+// 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+/**
+ * A provider for compilation prerequisites of a given target.
+ */
+@Immutable
+public final class CompilationPrerequisitesProvider implements TransitiveInfoProvider {
+
+ private final NestedSet<Artifact> compilationPrerequisites;
+
+ public CompilationPrerequisitesProvider(NestedSet<Artifact> compilationPrerequisites) {
+ this.compilationPrerequisites = compilationPrerequisites;
+ }
+
+ /**
+ * Returns compilation prerequisites (e.g., generated sources and headers) for a rule.
+ */
+ public NestedSet<Artifact> getCompilationPrerequisites() {
+ return compilationPrerequisites;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfigurationCollectionFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfigurationCollectionFactory.java
new file mode 100644
index 0000000..8ffac43
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfigurationCollectionFactory.java
@@ -0,0 +1,54 @@
+// 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.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.analysis.config.ConfigurationFactory;
+import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
+import com.google.devtools.build.lib.analysis.config.PackageProviderForConfigurations;
+import com.google.devtools.build.lib.events.EventHandler;
+
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/**
+ * A factory for configuration collection creation.
+ */
+public interface ConfigurationCollectionFactory {
+ /**
+ * Creates the top-level configuration for a build.
+ *
+ * <p>Also it may create a set of BuildConfigurations and define a transition table over them.
+ * All configurations during a build should be accessible from this top-level configuration
+ * via configuration transitions.
+ * @param loadedPackageProvider the package provider
+ * @param buildOptions top-level build options representing the command-line
+ * @param clientEnv the system environment
+ * @param errorEventListener the event listener for errors
+ * @param performSanityCheck flag to signal about performing sanity check. Can be false only for
+ * tests in skyframe. Legacy mode uses loadedPackageProvider == null condition for this.
+ * @return the top-level configuration
+ * @throws InvalidConfigurationException
+ */
+ @Nullable
+ public BuildConfiguration createConfigurations(
+ ConfigurationFactory configurationFactory,
+ PackageProviderForConfigurations loadedPackageProvider,
+ BuildOptions buildOptions,
+ Map<String, String> clientEnv,
+ EventHandler errorEventListener,
+ boolean performSanityCheck) throws InvalidConfigurationException;
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfigurationMakeVariableContext.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfigurationMakeVariableContext.java
new file mode 100644
index 0000000..8c98f5f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfigurationMakeVariableContext.java
@@ -0,0 +1,56 @@
+// 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.devtools.build.lib.analysis.MakeVariableExpander.ExpansionException;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.packages.Package;
+
+import java.util.Map;
+
+/**
+ * Implements make variable expansion for make variables that depend on the
+ * configuration and the target (not on behavior of the
+ * {@link ConfiguredTarget} implementation)
+ */
+public class ConfigurationMakeVariableContext implements MakeVariableExpander.Context {
+ private final Package pkg;
+ private final Map<String, String> commandLineEnv;
+ private final Map<String, String> globalEnv;
+ private final String platform;
+
+ public ConfigurationMakeVariableContext(Package pkg, BuildConfiguration configuration) {
+ this.pkg = pkg;
+ commandLineEnv = configuration.getCommandLineDefines();
+ globalEnv = configuration.getGlobalMakeEnvironment();
+ platform = configuration.getPlatformName();
+ }
+
+ @Override
+ public String lookupMakeVariable(String var) throws ExpansionException {
+ String value = commandLineEnv.get(var);
+ if (value == null) {
+ value = pkg.lookupMakeVariable(var, platform);
+ }
+ if (value == null) {
+ value = globalEnv.get(var);
+ }
+ if (value == null) {
+ throw new MakeVariableExpander.ExpansionException("$(" + var + ") not defined");
+ }
+
+ return value;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfigurationsCreatedEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfigurationsCreatedEvent.java
new file mode 100644
index 0000000..8b0621a
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfigurationsCreatedEvent.java
@@ -0,0 +1,38 @@
+// 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.devtools.build.lib.analysis.config.BuildConfigurationCollection;
+
+/**
+ * This event is fired when the build configurations are created.
+ */
+public class ConfigurationsCreatedEvent {
+
+ private final BuildConfigurationCollection configurations;
+
+ /**
+ * Construct the ConfigurationsCreatedEvent.
+ *
+ * @param configurations the build configuration collection
+ */
+ public ConfigurationsCreatedEvent(BuildConfigurationCollection configurations) {
+ this.configurations = configurations;
+ }
+
+ public BuildConfigurationCollection getBuildConfigurationCollection() {
+ return configurations;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspectFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspectFactory.java
new file mode 100644
index 0000000..955241a
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspectFactory.java
@@ -0,0 +1,28 @@
+// 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.devtools.build.lib.packages.AspectFactory;
+
+/**
+ * Instantiation of {@link AspectFactory} with the actual types.
+ *
+ * <p>This is needed because {@link AspectFactory} is needed in the {@code packages} package to
+ * do loading phase things properly and to be able to specify them on attributes, but the actual
+ * classes are in the {@code view} package, which is not available there.
+ */
+public interface ConfiguredAspectFactory
+ extends AspectFactory<ConfiguredTarget, RuleContext, Aspect> {
+
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAttributeMapper.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAttributeMapper.java
new file mode 100644
index 0000000..84029b2
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAttributeMapper.java
@@ -0,0 +1,158 @@
+// 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.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
+import com.google.devtools.build.lib.packages.AbstractAttributeMapper;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.Label;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * {@link AttributeMap} implementation that binds a rule's attribute as follows:
+ *
+ * <ol>
+ * <li>If the attribute is selectable (i.e. its BUILD declaration is of the form
+ * "attr = { config1: "value1", "config2: "value2", ... }", returns the subset of values
+ * chosen by the current configuration in accordance with Bazel's documented policy on
+ * configurable attribute selection.
+ * <li>If the attribute is not selectable (i.e. its value is static), returns that value with
+ * no additional processing.
+ * </ol>
+ *
+ * <p>Example usage:
+ * <pre>
+ * Label fooLabel = ConfiguredAttributeMapper.of(ruleConfiguredTarget).get("foo", Type.LABEL);
+ * </pre>
+ */
+public class ConfiguredAttributeMapper extends AbstractAttributeMapper {
+
+ private final Map<Label, ConfigMatchingProvider> configConditions;
+ private Rule rule;
+
+ private ConfiguredAttributeMapper(Rule rule, Set<ConfigMatchingProvider> configConditions) {
+ super(Preconditions.checkNotNull(rule).getPackage(), rule.getRuleClassObject(), rule.getLabel(),
+ rule.getAttributeContainer());
+ ImmutableMap.Builder<Label, ConfigMatchingProvider> builder = ImmutableMap.builder();
+ for (ConfigMatchingProvider configCondition : configConditions) {
+ builder.put(configCondition.label(), configCondition);
+ }
+ this.configConditions = builder.build();
+ this.rule = rule;
+ }
+
+ /**
+ * "Do-it-all" constructor that just needs a {@link RuleConfiguredTarget}.
+ */
+ public static ConfiguredAttributeMapper of(RuleConfiguredTarget ct) {
+ return new ConfiguredAttributeMapper(ct.getTarget(), ct.getConfigConditions());
+ }
+
+ /**
+ * "Manual" constructor that requires the caller to pass the set of configurability conditions
+ * that trigger this rule's configurable attributes.
+ *
+ * <p>If you don't know how to do this, you really want to use one of the "do-it-all"
+ * constructors.
+ */
+ static ConfiguredAttributeMapper of(Rule rule, Set<ConfigMatchingProvider> configConditions) {
+ return new ConfiguredAttributeMapper(rule, configConditions);
+ }
+
+ /**
+ * Checks that all attributes can be mapped to their configured values. This is
+ * useful for checking that the configuration space in a configured attribute doesn't
+ * contain unresolvable contradictions.
+ *
+ * @throws EvalException if any attribute's value can't be resolved under this mapper
+ */
+ public void validateAttributes() throws EvalException {
+ for (String attrName : getAttributeNames()) {
+ getAndValidate(attrName, getAttributeType(attrName));
+ }
+ }
+
+ /**
+ * Variation of {@link #get} that throws an informative exception if the attribute
+ * can't be resolved due to intrinsic contradictions in the configuration.
+ */
+ private <T> T getAndValidate(String attributeName, Type<T> type) throws EvalException {
+ Type.Selector<T> selector = getSelector(attributeName, type);
+ if (selector == null) {
+ // This is a normal attribute.
+ return super.get(attributeName, type);
+ }
+
+ // We expect exactly one of this attribute's conditions to match (including the default
+ // condition, if specified). Throw an exception if our expectations aren't met.
+ Label matchingCondition = null;
+ T matchingValue = null;
+
+ // Find the matching condition and record its value (checking for duplicates).
+ for (Map.Entry<Label, T> entry : selector.getEntries().entrySet()) {
+ Label curCondition = entry.getKey();
+ if (Type.Selector.isReservedLabel(curCondition)) {
+ continue;
+ } else if (Preconditions.checkNotNull(configConditions.get(curCondition)).matches()) {
+ if (matchingCondition != null) {
+ throw new EvalException(rule.getAttributeLocation(attributeName),
+ "Both " + matchingCondition.toString() + " and " + curCondition.toString()
+ + " match configurable attribute \"" + attributeName + "\" in " + getLabel()
+ + ". At most one match is allowed");
+ }
+ matchingCondition = curCondition;
+ matchingValue = entry.getValue();
+ }
+ }
+
+ // If nothing matched, choose the default condition.
+ if (matchingCondition == null) {
+ if (!selector.hasDefault()) {
+ throw new EvalException(rule.getAttributeLocation(attributeName),
+ "Configurable attribute \"" + attributeName + "\" doesn't match this "
+ + "configuration (would a default condition help?)");
+ }
+ matchingValue = selector.getDefault();
+ }
+
+ return matchingValue;
+ }
+
+ @Override
+ public <T> T get(String attributeName, Type<T> type) {
+ try {
+ return getAndValidate(attributeName, type);
+ } catch (EvalException e) {
+ // Callers that reach this branch should explicitly validate the attribute through an
+ // appropriate call and handle the exception directly. This method assumes
+ // pre-validated attributes.
+ throw new IllegalStateException(
+ "lookup failed on attribute " + attributeName + ": " + e.getMessage());
+ }
+ }
+
+ @Override
+ protected <T> Iterable<T> visitAttribute(String attributeName, Type<T> type) {
+ T value = get(attributeName, type);
+ return value == null ? ImmutableList.<T>of() : ImmutableList.of(value);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
new file mode 100644
index 0000000..e84f9aa
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
@@ -0,0 +1,376 @@
+// 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 static com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType.ABSTRACT;
+import static com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType.TEST;
+
+import com.google.common.base.Preconditions;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory;
+import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory;
+import com.google.devtools.build.lib.analysis.config.DefaultsPackage;
+import com.google.devtools.build.lib.analysis.config.FragmentOptions;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.graph.Digraph;
+import com.google.devtools.build.lib.graph.Node;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleClassProvider;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+import com.google.devtools.build.lib.rules.SkylarkModules;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
+import com.google.devtools.build.lib.syntax.SkylarkModule;
+import com.google.devtools.build.lib.syntax.SkylarkType;
+import com.google.devtools.build.lib.syntax.ValidationEnvironment;
+import com.google.devtools.common.options.OptionsClassProvider;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Knows about every rule Blaze supports and the associated configuration options.
+ *
+ * <p>This class is initialized on server startup and the set of rules, build info factories
+ * and configuration options is guarantees not to change over the life time of the Blaze server.
+ */
+public class ConfiguredRuleClassProvider implements RuleClassProvider {
+ /**
+ * Custom dependency validation logic.
+ */
+ public static interface PrerequisiteValidator {
+ /**
+ * Checks whether the rule in {@code contextBuilder} is allowed to depend on
+ * {@code prerequisite} through the attribute {@code attribute}.
+ *
+ * <p>Can be used for enforcing any organization-specific policies about the layout of the
+ * workspace.
+ */
+ void validate(
+ RuleContext.Builder contextBuilder, ConfiguredTarget prerequisite, Attribute attribute);
+ }
+
+ /**
+ * Builder for {@link ConfiguredRuleClassProvider}.
+ */
+ public static class Builder implements RuleDefinitionEnvironment {
+ private final List<ConfigurationFragmentFactory> configurationFragments = new ArrayList<>();
+ private final List<BuildInfoFactory> buildInfoFactories = new ArrayList<>();
+ private final List<Class<? extends FragmentOptions>> configurationOptions = new ArrayList<>();
+
+ private final Map<String, RuleClass> ruleClassMap = new HashMap<>();
+ private final Map<String, Class<? extends RuleDefinition>> ruleDefinitionMap =
+ new HashMap<>();
+ private final Map<Class<? extends RuleDefinition>, RuleClass> ruleMap = new HashMap<>();
+ private final Digraph<Class<? extends RuleDefinition>> dependencyGraph =
+ new Digraph<>();
+ private ConfigurationCollectionFactory configurationCollectionFactory;
+ private PrerequisiteValidator prerequisiteValidator;
+ private ImmutableMap<String, SkylarkType> skylarkAccessibleJavaClasses = ImmutableMap.of();
+
+ public Builder setPrerequisiteValidator(PrerequisiteValidator prerequisiteValidator) {
+ this.prerequisiteValidator = prerequisiteValidator;
+ return this;
+ }
+
+ public Builder addBuildInfoFactory(BuildInfoFactory factory) {
+ buildInfoFactories.add(factory);
+ return this;
+ }
+
+ public Builder addRuleDefinition(Class<? extends RuleDefinition> ruleDefinition) {
+ dependencyGraph.createNode(ruleDefinition);
+ BlazeRule annotation = ruleDefinition.getAnnotation(BlazeRule.class);
+ for (Class<? extends RuleDefinition> ancestor : annotation.ancestors()) {
+ dependencyGraph.addEdge(ancestor, ruleDefinition);
+ }
+
+ return this;
+ }
+
+ public Builder addConfigurationOptions(Class<? extends FragmentOptions> configurationOptions) {
+ this.configurationOptions.add(configurationOptions);
+ return this;
+ }
+
+ public Builder addConfigurationFragment(ConfigurationFragmentFactory factory) {
+ configurationFragments.add(factory);
+ return this;
+ }
+
+ public Builder setConfigurationCollectionFactory(ConfigurationCollectionFactory factory) {
+ this.configurationCollectionFactory = factory;
+ return this;
+ }
+
+ public Builder setSkylarkAccessibleJavaClasses(ImmutableMap<String, SkylarkType> objects) {
+ this.skylarkAccessibleJavaClasses = objects;
+ return this;
+ }
+
+ private RuleConfiguredTargetFactory createFactory(
+ Class<? extends RuleConfiguredTargetFactory> factoryClass) {
+ try {
+ Constructor<? extends RuleConfiguredTargetFactory> ctor = factoryClass.getConstructor();
+ return ctor.newInstance();
+ } catch (NoSuchMethodException | IllegalAccessException | InstantiationException
+ | InvocationTargetException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private RuleClass commitRuleDefinition(Class<? extends RuleDefinition> definitionClass) {
+ BlazeRule annotation = definitionClass.getAnnotation(BlazeRule.class);
+ Preconditions.checkArgument(ruleClassMap.get(annotation.name()) == null, annotation.name());
+
+ Preconditions.checkArgument(
+ annotation.type() == ABSTRACT ^
+ annotation.factoryClass() != RuleConfiguredTargetFactory.class);
+ Preconditions.checkArgument(
+ (annotation.type() != TEST) ||
+ Arrays.asList(annotation.ancestors()).contains(
+ BaseRuleClasses.TestBaseRule.class));
+
+ RuleDefinition instance;
+ try {
+ instance = definitionClass.newInstance();
+ } catch (IllegalAccessException | InstantiationException e) {
+ throw new IllegalStateException(e);
+ }
+ RuleClass[] ancestorClasses = new RuleClass[annotation.ancestors().length];
+ for (int i = 0; i < annotation.ancestors().length; i++) {
+ ancestorClasses[i] = ruleMap.get(annotation.ancestors()[i]);
+ if (ancestorClasses[i] == null) {
+ // Ancestors should have been initialized by now
+ throw new IllegalStateException("Ancestor " + annotation.ancestors()[i] + " of "
+ + annotation.name() + " is not initialized");
+ }
+ }
+
+ RuleConfiguredTargetFactory factory = null;
+ if (annotation.type() != ABSTRACT) {
+ factory = createFactory(annotation.factoryClass());
+ }
+
+ RuleClass.Builder builder = new RuleClass.Builder(
+ annotation.name(), annotation.type(), false, ancestorClasses);
+ builder.factory(factory);
+ RuleClass ruleClass = instance.build(builder, this);
+ ruleMap.put(definitionClass, ruleClass);
+ ruleClassMap.put(ruleClass.getName(), ruleClass);
+ ruleDefinitionMap.put(ruleClass.getName(), definitionClass);
+
+ return ruleClass;
+ }
+
+ public ConfiguredRuleClassProvider build() {
+ for (Node<Class<? extends RuleDefinition>> ruleDefinition :
+ dependencyGraph.getTopologicalOrder()) {
+ commitRuleDefinition(ruleDefinition.getLabel());
+ }
+
+ return new ConfiguredRuleClassProvider(
+ ImmutableMap.copyOf(ruleClassMap),
+ ImmutableMap.copyOf(ruleDefinitionMap),
+ ImmutableList.copyOf(buildInfoFactories),
+ ImmutableList.copyOf(configurationOptions),
+ ImmutableList.copyOf(configurationFragments),
+ configurationCollectionFactory,
+ prerequisiteValidator,
+ skylarkAccessibleJavaClasses);
+ }
+
+ @Override
+ public Label getLabel(String labelValue) {
+ return LABELS.getUnchecked(labelValue);
+ }
+ }
+
+ /**
+ * Used to make the label instances unique, so that we don't create a new
+ * instance for every rule.
+ */
+ private static LoadingCache<String, Label> LABELS = CacheBuilder.newBuilder().build(
+ new CacheLoader<String, Label>() {
+ @Override
+ public Label load(String from) {
+ try {
+ return Label.parseAbsolute(from);
+ } catch (Label.SyntaxException e) {
+ throw new IllegalArgumentException(from);
+ }
+ }
+ });
+
+ /**
+ * Maps rule class name to the metaclass instance for that rule.
+ */
+ private final ImmutableMap<String, RuleClass> ruleClassMap;
+
+ /**
+ * Maps rule class name to the rule definition metaclasses.
+ */
+ private final ImmutableMap<String, Class<? extends RuleDefinition>> ruleDefinitionMap;
+
+ /**
+ * The configuration options that affect the behavior of the rules.
+ */
+ private final ImmutableList<Class<? extends FragmentOptions>> configurationOptions;
+
+ /**
+ * The set of configuration fragment factories.
+ */
+ private final ImmutableList<ConfigurationFragmentFactory> configurationFragments;
+
+ /**
+ * The factory that creates the configuration collection.
+ */
+ private final ConfigurationCollectionFactory configurationCollectionFactory;
+
+ private final ImmutableList<BuildInfoFactory> buildInfoFactories;
+
+ private final PrerequisiteValidator prerequisiteValidator;
+
+ private final ImmutableMap<String, SkylarkType> skylarkAccessibleJavaClasses;
+
+ private final ValidationEnvironment skylarkValidationEnvironment;
+
+ public ConfiguredRuleClassProvider(
+ ImmutableMap<String, RuleClass> ruleClassMap,
+ ImmutableMap<String, Class<? extends RuleDefinition>> ruleDefinitionMap,
+ ImmutableList<BuildInfoFactory> buildInfoFactories,
+ ImmutableList<Class<? extends FragmentOptions>> configurationOptions,
+ ImmutableList<ConfigurationFragmentFactory> configurationFragments,
+ ConfigurationCollectionFactory configurationCollectionFactory,
+ PrerequisiteValidator prerequisiteValidator,
+ ImmutableMap<String, SkylarkType> skylarkAccessibleJavaClasses) {
+
+ this.ruleClassMap = ruleClassMap;
+ this.ruleDefinitionMap = ruleDefinitionMap;
+ this.buildInfoFactories = buildInfoFactories;
+ this.configurationOptions = configurationOptions;
+ this.configurationFragments = configurationFragments;
+ this.configurationCollectionFactory = configurationCollectionFactory;
+ this.prerequisiteValidator = prerequisiteValidator;
+ this.skylarkAccessibleJavaClasses = skylarkAccessibleJavaClasses;
+ this.skylarkValidationEnvironment = SkylarkModules.getValidationEnvironment(
+ ImmutableMap.<String, SkylarkType>builder()
+ .putAll(skylarkAccessibleJavaClasses)
+ .put("native", SkylarkType.of(NativeModule.class))
+ .build());
+ }
+
+ public PrerequisiteValidator getPrerequisiteValidator() {
+ return prerequisiteValidator;
+ }
+
+ @Override
+ public Map<String, RuleClass> getRuleClassMap() {
+ return ruleClassMap;
+ }
+
+ /**
+ * Returns a list of build info factories that are needed for the supported languages.
+ */
+ public ImmutableList<BuildInfoFactory> getBuildInfoFactories() {
+ return buildInfoFactories;
+ }
+
+ /**
+ * Returns the set of configuration fragments provided by this module.
+ */
+ public ImmutableList<ConfigurationFragmentFactory> getConfigurationFragments() {
+ return configurationFragments;
+ }
+
+ /**
+ * Returns the set of configuration options that are supported in this module.
+ */
+ public ImmutableList<Class<? extends FragmentOptions>> getConfigurationOptions() {
+ return configurationOptions;
+ }
+
+ /**
+ * Returns the definition of the rule class definition with the specified name.
+ */
+ public Class<? extends RuleDefinition> getRuleClassDefinition(String ruleClassName) {
+ return ruleDefinitionMap.get(ruleClassName);
+ }
+
+ /**
+ * Returns the configuration collection creator.
+ */
+ public ConfigurationCollectionFactory getConfigurationCollectionFactory() {
+ return configurationCollectionFactory;
+ }
+
+ /**
+ * Returns the defaults package for the default settings.
+ */
+ public String getDefaultsPackageContent() {
+ return DefaultsPackage.getDefaultsPackageContent(configurationOptions);
+ }
+
+ /**
+ * Returns the defaults package for the given options taken from an optionsProvider.
+ */
+ public String getDefaultsPackageContent(OptionsClassProvider optionsProvider) {
+ return DefaultsPackage.getDefaultsPackageContent(
+ BuildOptions.of(configurationOptions, optionsProvider));
+ }
+
+ /**
+ * Creates a BuildOptions class for the given options taken from an optionsProvider.
+ */
+ public BuildOptions createBuildOptions(OptionsClassProvider optionsProvider) {
+ return BuildOptions.of(configurationOptions, optionsProvider);
+ }
+
+ @SkylarkModule(name = "native", namespace = true, onlyLoadingPhase = true,
+ doc = "Module for native rules.")
+ private static final class NativeModule {}
+
+ public static final NativeModule nativeModule = new NativeModule();
+
+ @Override
+ public SkylarkEnvironment createSkylarkRuleClassEnvironment(
+ EventHandler eventHandler, String astFileContentHashCode) {
+ SkylarkEnvironment env = SkylarkModules.getNewEnvironment(eventHandler, astFileContentHashCode);
+ for (Map.Entry<String, SkylarkType> entry : skylarkAccessibleJavaClasses.entrySet()) {
+ env.update(entry.getKey(), entry.getValue().getType());
+ }
+ return env;
+ }
+
+ @Override
+ public ValidationEnvironment getSkylarkValidationEnvironment() {
+ return skylarkValidationEnvironment;
+ }
+
+ @Override
+ public Object getNativeModule() {
+ return nativeModule;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTarget.java
new file mode 100644
index 0000000..7aad367
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTarget.java
@@ -0,0 +1,47 @@
+// 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.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.packages.Target;
+
+import javax.annotation.Nullable;
+
+/**
+ * A {@link ConfiguredTarget} is conceptually a {@link TransitiveInfoCollection} coupled
+ * with the {@link Target} and {@link BuildConfiguration} objects it was created from.
+ *
+ * <p>This interface is supposed to only be used in {@link BuildView} and above. In particular,
+ * rule implementations should not be able to access the {@link ConfiguredTarget} objects
+ * associated with their direct dependencies, only the corresponding
+ * {@link TransitiveInfoCollection}s. Also, {@link ConfiguredTarget} objects should not be
+ * accessible from the action graph.
+ */
+public interface ConfiguredTarget extends TransitiveInfoCollection {
+ /**
+ * Returns the Target with which this {@link ConfiguredTarget} is associated.
+ */
+ Target getTarget();
+
+ /**
+ * <p>Returns the {@link BuildConfiguration} for which this {@link ConfiguredTarget} is
+ * defined. Configuration is defined for all configured targets with exception
+ * of the {@link InputFileConfiguredTarget} for which it is always
+ * <b>null</b>.</p>
+ */
+ @Override
+ @Nullable
+ BuildConfiguration getConfiguration();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java
new file mode 100644
index 0000000..d265533
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java
@@ -0,0 +1,302 @@
+// 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.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ListMultimap;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ArtifactFactory;
+import com.google.devtools.build.lib.actions.ArtifactOwner;
+import com.google.devtools.build.lib.actions.FailAction;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment;
+import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
+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.concurrent.ThreadSafety.ThreadSafe;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.ConstantRuleVisibility;
+import com.google.devtools.build.lib.packages.InputFile;
+import com.google.devtools.build.lib.packages.OutputFile;
+import com.google.devtools.build.lib.packages.PackageGroup;
+import com.google.devtools.build.lib.packages.PackageGroupsRuleVisibility;
+import com.google.devtools.build.lib.packages.PackageSpecification;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleVisibility;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.rules.SkylarkRuleConfiguredTargetBuilder;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * This class creates {@link ConfiguredTarget} instances using a given {@link
+ * ConfiguredRuleClassProvider}.
+ */
+@ThreadSafe
+public final class ConfiguredTargetFactory {
+ // This class is not meant to be outside of the analysis phase machinery and is only public
+ // in order to be accessible from the .view.skyframe package.
+
+ private final ConfiguredRuleClassProvider ruleClassProvider;
+
+ public ConfiguredTargetFactory(ConfiguredRuleClassProvider ruleClassProvider) {
+ this.ruleClassProvider = ruleClassProvider;
+ }
+
+ /**
+ * Returns the visibility of the given target. Errors during package group resolution are reported
+ * to the {@code AnalysisEnvironment}.
+ */
+ private NestedSet<PackageSpecification> convertVisibility(
+ ListMultimap<Attribute, ConfiguredTarget> prerequisiteMap, EventHandler reporter,
+ Target target, BuildConfiguration packageGroupConfiguration) {
+ RuleVisibility ruleVisibility = target.getVisibility();
+ if (ruleVisibility instanceof ConstantRuleVisibility) {
+ return ((ConstantRuleVisibility) ruleVisibility).isPubliclyVisible()
+ ? NestedSetBuilder.<PackageSpecification>create(
+ Order.STABLE_ORDER, PackageSpecification.EVERYTHING)
+ : NestedSetBuilder.<PackageSpecification>emptySet(Order.STABLE_ORDER);
+ } else if (ruleVisibility instanceof PackageGroupsRuleVisibility) {
+ PackageGroupsRuleVisibility packageGroupsVisibility =
+ (PackageGroupsRuleVisibility) ruleVisibility;
+
+ NestedSetBuilder<PackageSpecification> packageSpecifications =
+ NestedSetBuilder.stableOrder();
+ for (Label groupLabel : packageGroupsVisibility.getPackageGroups()) {
+ // PackageGroupsConfiguredTargets are always in the package-group configuration.
+ ConfiguredTarget group =
+ findPrerequisite(prerequisiteMap, groupLabel, packageGroupConfiguration);
+ PackageSpecificationProvider provider = null;
+ // group == null can only happen if the package group list comes
+ // from a default_visibility attribute, because in every other case,
+ // this missing link is caught during transitive closure visitation or
+ // if the RuleConfiguredTargetGraph threw out a visibility edge
+ // because if would have caused a cycle. The filtering should be done
+ // in a single place, ConfiguredTargetGraph, but for now, this is the
+ // minimally invasive way of providing a sane error message in case a
+ // cycle is created by a visibility attribute.
+ if (group != null) {
+ provider = group.getProvider(PackageSpecificationProvider.class);
+ }
+ if (provider != null) {
+ packageSpecifications.addTransitive(provider.getPackageSpecifications());
+ } else {
+ reporter.handle(Event.error(target.getLocation(),
+ String.format("Label '%s' does not refer to a package group", groupLabel)));
+ }
+ }
+
+ packageSpecifications.addAll(packageGroupsVisibility.getDirectPackages());
+ return packageSpecifications.build();
+ } else {
+ throw new IllegalStateException("unknown visibility");
+ }
+ }
+
+ private ConfiguredTarget findPrerequisite(
+ ListMultimap<Attribute, ConfiguredTarget> prerequisiteMap, Label label,
+ BuildConfiguration config) {
+ for (ConfiguredTarget prerequisite : prerequisiteMap.get(null)) {
+ if (prerequisite.getLabel().equals(label) && (prerequisite.getConfiguration() == config)) {
+ return prerequisite;
+ }
+ }
+ return null;
+ }
+
+ private Artifact getOutputArtifact(OutputFile outputFile, BuildConfiguration configuration,
+ boolean isFileset, ArtifactFactory artifactFactory) {
+ Rule rule = outputFile.getAssociatedRule();
+ Root root = rule.hasBinaryOutput()
+ ? configuration.getBinDirectory()
+ : configuration.getGenfilesDirectory();
+ ArtifactOwner owner =
+ new ConfiguredTargetKey(rule.getLabel(), configuration.getArtifactOwnerConfiguration());
+ PathFragment rootRelativePath = Util.getWorkspaceRelativePath(outputFile);
+ Artifact result = isFileset
+ ? artifactFactory.getFilesetArtifact(rootRelativePath, root, owner)
+ : artifactFactory.getDerivedArtifact(rootRelativePath, root, owner);
+ // The associated rule should have created the artifact.
+ Preconditions.checkNotNull(result, "no artifact for %s", rootRelativePath);
+ return result;
+ }
+
+ /**
+ * Invokes the appropriate constructor to create a {@link ConfiguredTarget} instance.
+ * <p>For use in {@code ConfiguredTargetFunction}.
+ *
+ * <p>Returns null if Skyframe deps are missing or upon certain errors.
+ */
+ @Nullable
+ public final ConfiguredTarget createConfiguredTarget(AnalysisEnvironment analysisEnvironment,
+ ArtifactFactory artifactFactory, Target target, BuildConfiguration config,
+ ListMultimap<Attribute, ConfiguredTarget> prerequisiteMap,
+ Set<ConfigMatchingProvider> configConditions)
+ throws InterruptedException {
+ if (target instanceof Rule) {
+ return createRule(
+ analysisEnvironment, (Rule) target, config, prerequisiteMap, configConditions);
+ }
+
+ // Visibility, like all package groups, doesn't have a configuration
+ NestedSet<PackageSpecification> visibility = convertVisibility(
+ prerequisiteMap, analysisEnvironment.getEventHandler(), target, null);
+ TargetContext targetContext = new TargetContext(analysisEnvironment, target, config,
+ prerequisiteMap.get(null), visibility);
+ if (target instanceof OutputFile) {
+ OutputFile outputFile = (OutputFile) target;
+ boolean isFileset = outputFile.getGeneratingRule().getRuleClass().equals("Fileset");
+ Artifact artifact = getOutputArtifact(outputFile, config, isFileset, artifactFactory);
+ TransitiveInfoCollection rule = targetContext.findDirectPrerequisite(
+ outputFile.getGeneratingRule().getLabel(), config);
+ if (isFileset) {
+ return new FilesetOutputConfiguredTarget(targetContext, outputFile, rule, artifact);
+ } else {
+ return new OutputFileConfiguredTarget(targetContext, outputFile, rule, artifact);
+ }
+ } else if (target instanceof InputFile) {
+ InputFile inputFile = (InputFile) target;
+ Artifact artifact = artifactFactory.getSourceArtifact(
+ inputFile.getExecPath(),
+ Root.asSourceRoot(inputFile.getPackage().getSourceRoot()),
+ new ConfiguredTargetKey(target.getLabel(), config));
+
+ return new InputFileConfiguredTarget(targetContext, inputFile, artifact);
+ } else if (target instanceof PackageGroup) {
+ PackageGroup packageGroup = (PackageGroup) target;
+ return new PackageGroupConfiguredTarget(targetContext, packageGroup);
+ } else {
+ throw new AssertionError("Unexpected target class: " + target.getClass().getName());
+ }
+ }
+
+ /**
+ * Factory method: constructs a RuleConfiguredTarget of the appropriate class, based on the rule
+ * class. May return null if an error occurred.
+ */
+ @Nullable
+ private ConfiguredTarget createRule(
+ AnalysisEnvironment env, Rule rule, BuildConfiguration configuration,
+ ListMultimap<Attribute, ConfiguredTarget> prerequisiteMap,
+ Set<ConfigMatchingProvider> configConditions) throws InterruptedException {
+ // Visibility computation and checking is done for every rule.
+ RuleContext ruleContext = new RuleContext.Builder(env, rule, configuration,
+ ruleClassProvider.getPrerequisiteValidator())
+ .setVisibility(convertVisibility(prerequisiteMap, env.getEventHandler(), rule, null))
+ .setPrerequisites(prerequisiteMap)
+ .setConfigConditions(configConditions)
+ .build();
+ if (ruleContext.hasErrors()) {
+ return null;
+ }
+ if (!rule.getRuleClassObject().getRequiredConfigurationFragments().isEmpty()) {
+ if (!configuration.hasAllFragments(
+ rule.getRuleClassObject().getRequiredConfigurationFragments())) {
+ if (rule.getRuleClassObject().failIfMissingConfigurationFragment()) {
+ ruleContext.ruleError(missingFragmentError(ruleContext));
+ return null;
+ }
+ return createFailConfiguredTarget(ruleContext);
+ }
+ }
+ if (rule.getRuleClassObject().isSkylarkExecutable()) {
+ // TODO(bazel-team): maybe merge with RuleConfiguredTargetBuilder?
+ return SkylarkRuleConfiguredTargetBuilder.buildRule(
+ ruleContext, rule.getRuleClassObject().getConfiguredTargetFunction());
+ } else {
+ RuleClass.ConfiguredTargetFactory<ConfiguredTarget, RuleContext> factory =
+ rule.getRuleClassObject().<ConfiguredTarget, RuleContext>getConfiguredTargetFactory();
+ Preconditions.checkNotNull(factory, rule.getRuleClassObject());
+ return factory.create(ruleContext);
+ }
+ }
+
+ private String missingFragmentError(RuleContext ruleContext) {
+ RuleClass ruleClass = ruleContext.getRule().getRuleClassObject();
+ Set<Class<?>> missingFragments = new LinkedHashSet<>();
+ for (Class<?> fragment : ruleClass.getRequiredConfigurationFragments()) {
+ if (!ruleContext.getConfiguration().hasFragment(fragment.asSubclass(Fragment.class))) {
+ missingFragments.add(fragment);
+ }
+ }
+ Preconditions.checkState(!missingFragments.isEmpty());
+ StringBuilder result = new StringBuilder();
+ result.append("all rules of type " + ruleClass.getName() + " require the presence of ");
+ List<String> names = new ArrayList<>();
+ for (Class<?> fragment : missingFragments) {
+ // TODO(bazel-team): Using getSimpleName here is sub-optimal, but we don't have anything
+ // better right now.
+ names.add(fragment.getSimpleName());
+ }
+ result.append("all of [");
+ result.append(Joiner.on(",").join(names));
+ result.append("], but these were all disabled");
+ return result.toString();
+ }
+
+ /**
+ * Constructs an {@link Aspect}. Returns null if an error occurs; in that case,
+ * {@code aspectFactory} should call one of the error reporting methods of {@link RuleContext}.
+ */
+ public Aspect createAspect(
+ AnalysisEnvironment env, RuleConfiguredTarget associatedTarget,
+ ConfiguredAspectFactory aspectFactory,
+ ListMultimap<Attribute, ConfiguredTarget> prerequisiteMap,
+ Set<ConfigMatchingProvider> configConditions) {
+ RuleContext.Builder builder = new RuleContext.Builder(env,
+ associatedTarget.getTarget(),
+ associatedTarget.getConfiguration(),
+ ruleClassProvider.getPrerequisiteValidator());
+ RuleContext ruleContext = builder
+ .setVisibility(convertVisibility(
+ prerequisiteMap, env.getEventHandler(), associatedTarget.getTarget(), null))
+ .setPrerequisites(prerequisiteMap)
+ .setConfigConditions(configConditions)
+ .build();
+ if (ruleContext.hasErrors()) {
+ return null;
+ }
+
+ return aspectFactory.create(associatedTarget, ruleContext);
+ }
+
+ /**
+ * A pseudo-implementation for configured targets that creates fail actions for all declared
+ * outputs, both implicit and explicit.
+ */
+ private static ConfiguredTarget createFailConfiguredTarget(RuleContext ruleContext) {
+ RuleConfiguredTargetBuilder builder = new RuleConfiguredTargetBuilder(ruleContext);
+ if (!ruleContext.getOutputArtifacts().isEmpty()) {
+ ruleContext.registerAction(new FailAction(ruleContext.getActionOwner(),
+ ruleContext.getOutputArtifacts(), "Can't build this"));
+ }
+ builder.add(RunfilesProvider.class, RunfilesProvider.simple(Runfiles.EMPTY));
+ return builder.build();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java b/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java
new file mode 100644
index 0000000..64cddb1
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/DependencyResolver.java
@@ -0,0 +1,573 @@
+// 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.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Sets;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
+import com.google.devtools.build.lib.collect.ImmutableSortedKeyListMultimap;
+import com.google.devtools.build.lib.packages.AspectDefinition;
+import com.google.devtools.build.lib.packages.AspectFactory;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition;
+import com.google.devtools.build.lib.packages.Attribute.LateBoundDefault;
+import com.google.devtools.build.lib.packages.Attribute.SplitTransition;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.EnvironmentGroup;
+import com.google.devtools.build.lib.packages.InputFile;
+import com.google.devtools.build.lib.packages.NoSuchThingException;
+import com.google.devtools.build.lib.packages.OutputFile;
+import com.google.devtools.build.lib.packages.PackageGroup;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.Label;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import javax.annotation.Nullable;
+
+/**
+ * Resolver for dependencies between configured targets.
+ *
+ * <p>Includes logic to derive the right configurations depending on transition type.
+ */
+public abstract class DependencyResolver {
+ /**
+ * A dependency of a configured target through a label.
+ *
+ * <p>Includes the target and the configuration of the dependency configured target and any
+ * aspects that may be required.
+ *
+ * <p>Note that the presence of an aspect here does not necessarily mean that it will be created.
+ * They will be filtered based on the {@link TransitiveInfoProvider} instances their associated
+ * configured targets have (we cannot do that here because the configured targets are not
+ * available yet). No error or warning is reported in this case, because it is expected that rules
+ * sometimes over-approximate the providers they supply in their definitions.
+ */
+ public static final class Dependency {
+
+ /**
+ * Returns the {@link ConfiguredTargetKey} for a direct dependency.
+ *
+ * <p>Essentially the same information as {@link Dependency} minus the aspects.
+ */
+ public static final Function<Dependency, ConfiguredTargetKey>
+ TO_CONFIGURED_TARGET_KEY = new Function<Dependency, ConfiguredTargetKey>() {
+ @Override
+ public ConfiguredTargetKey apply(Dependency input) {
+ return new ConfiguredTargetKey(input.getLabel(), input.getConfiguration());
+ }
+ };
+
+ private final Label label;
+ private final BuildConfiguration configuration;
+ private final ImmutableSet<Class<? extends ConfiguredAspectFactory>> aspects;
+
+ public Dependency(Label label, BuildConfiguration configuration,
+ ImmutableSet<Class<? extends ConfiguredAspectFactory>> aspects) {
+ this.label = label;
+ this.configuration = configuration;
+ this.aspects = aspects;
+ }
+
+ public Dependency(Label label, BuildConfiguration configuration) {
+ this(label, configuration, ImmutableSet.<Class<? extends ConfiguredAspectFactory>>of());
+ }
+
+ public Label getLabel() {
+ return label;
+ }
+
+ public BuildConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ public ImmutableSet<Class<? extends ConfiguredAspectFactory>> getAspects() {
+ return aspects;
+ }
+ }
+
+ protected DependencyResolver() {
+ }
+
+ /**
+ * Returns ids for dependent nodes of a given node, sorted by attribute. Note that some
+ * dependencies do not have a corresponding attribute here, and we use the null attribute to
+ * represent those edges. Visibility attributes are only visited if {@code visitVisibility} is
+ * {@code true}.
+ *
+ * <p>If {@code aspect} is null, returns the dependent nodes of the configured target node
+ * representing the given target and configuration, otherwise that of the aspect node accompanying
+ * the aforementioned configured target node for the specified aspect.
+ *
+ * <p>The values are not simply labels because this also implements the first step of applying
+ * configuration transitions, namely, split transitions. This needs to be done before the labels
+ * are resolved because late bound attributes depend on the configuration. A good example for this
+ * is @{code :cc_toolchain}.
+ *
+ * <p>The long-term goal is that most configuration transitions be applied here. However, in order
+ * to do that, we first have to eliminate transitions that depend on the rule class of the
+ * dependency.
+ */
+ public final ListMultimap<Attribute, Dependency> dependentNodeMap(
+ TargetAndConfiguration node, AspectDefinition aspect,
+ Set<ConfigMatchingProvider> configConditions)
+ throws EvalException {
+ Target target = node.getTarget();
+ BuildConfiguration config = node.getConfiguration();
+ ListMultimap<Attribute, Dependency> outgoingEdges = ArrayListMultimap.create();
+ if (target instanceof OutputFile) {
+ Preconditions.checkNotNull(config);
+ visitTargetVisibility(node, outgoingEdges.get(null));
+ Rule rule = ((OutputFile) target).getGeneratingRule();
+ outgoingEdges.get(null).add(new Dependency(rule.getLabel(), config));
+ } else if (target instanceof InputFile) {
+ visitTargetVisibility(node, outgoingEdges.get(null));
+ } else if (target instanceof EnvironmentGroup) {
+ visitTargetVisibility(node, outgoingEdges.get(null));
+ } else if (target instanceof Rule) {
+ Preconditions.checkNotNull(config);
+ visitTargetVisibility(node, outgoingEdges.get(null));
+ Rule rule = (Rule) target;
+ ListMultimap<Attribute, LabelAndConfiguration> labelMap =
+ resolveAttributes(rule, aspect, config, configConditions);
+ visitRule(rule, aspect, labelMap, outgoingEdges);
+ } else if (target instanceof PackageGroup) {
+ visitPackageGroup(node, (PackageGroup) target, outgoingEdges.get(null));
+ } else {
+ throw new IllegalStateException(target.getLabel().toString());
+ }
+ return outgoingEdges;
+ }
+
+ private ListMultimap<Attribute, LabelAndConfiguration> resolveAttributes(
+ Rule rule, AspectDefinition aspect, BuildConfiguration configuration,
+ Set<ConfigMatchingProvider> configConditions)
+ throws EvalException {
+ ConfiguredAttributeMapper attributeMap = ConfiguredAttributeMapper.of(rule, configConditions);
+ attributeMap.validateAttributes();
+ List<Attribute> attributes;
+ if (aspect == null) {
+ attributes = rule.getRuleClassObject().getAttributes();
+ } else {
+ attributes = new ArrayList<>();
+ attributes.addAll(rule.getRuleClassObject().getAttributes());
+ if (aspect != null) {
+ attributes.addAll(aspect.getAttributes().values());
+ }
+ }
+
+ ImmutableSortedKeyListMultimap.Builder<Attribute, LabelAndConfiguration> result =
+ ImmutableSortedKeyListMultimap.builder();
+
+ resolveExplicitAttributes(rule, configuration, attributeMap, result);
+ resolveImplicitAttributes(rule, configuration, attributeMap, attributes, result);
+ resolveLateBoundAttributes(rule, configuration, attributeMap, attributes, result);
+
+ // Add the rule's visibility labels (which may come from the rule or from package defaults).
+ addExplicitDeps(result, rule, "visibility", rule.getVisibility().getDependencyLabels(),
+ configuration);
+
+ // Add package default constraints when the rule doesn't explicitly declare them.
+ //
+ // Note that this can have subtle implications for constraint semantics. For example: say that
+ // package defaults declare compatibility with ':foo' and rule R declares compatibility with
+ // ':bar'. Does that mean that R is compatible with [':foo', ':bar'] or just [':bar']? In other
+ // words, did R's author intend to add additional compatibility to the package defaults or to
+ // override them? More severely, what if package defaults "restrict" support to just [':baz']?
+ // Should R's declaration signify [':baz'] + ['bar'], [ORIGINAL_DEFAULTS] + ['bar'], or
+ // something else?
+ //
+ // Rather than try to answer these questions with possibly confusing logic, we take the
+ // simple approach of assigning the rule's "restriction" attribute to the rule-declared value if
+ // it exists, else the package defaults value (and likewise for "compatibility"). This may not
+ // always provide what users want, but it makes it easy for them to understand how rule
+ // declarations and package defaults intermix (and how to refactor them to get what they want).
+ //
+ // An alternative model would be to apply the "rule declaration" / "rule class defaults"
+ // relationship, i.e. the rule class' "compatibility" and "restriction" declarations are merged
+ // to generate a set of default environments, then the rule's declarations are independently
+ // processed on top of that. This protects against obscure coupling behavior between
+ // declarations from wildly different places (e.g. it offers clear answers to the examples posed
+ // above). But within the scope of a single package it seems better to keep the model simple and
+ // make the user responsible for resolving ambiguities.
+ if (!rule.isAttributeValueExplicitlySpecified(RuleClass.COMPATIBLE_ENVIRONMENT_ATTR)) {
+ addExplicitDeps(result, rule, RuleClass.COMPATIBLE_ENVIRONMENT_ATTR,
+ rule.getPackage().getDefaultCompatibleWith(), configuration);
+ }
+ if (!rule.isAttributeValueExplicitlySpecified(RuleClass.RESTRICTED_ENVIRONMENT_ATTR)) {
+ addExplicitDeps(result, rule, RuleClass.RESTRICTED_ENVIRONMENT_ATTR,
+ rule.getPackage().getDefaultRestrictedTo(), configuration);
+ }
+
+ return result.build();
+ }
+
+ /**
+ * Adds new dependencies to the given rule under the given attribute name
+ *
+ * @param result the builder for the attribute --> dependency labels map
+ * @param rule the rule being evaluated
+ * @param attrName the name of the attribute to add dependency labels to
+ * @param labels the dependencies to add
+ * @param configuration the configuration to apply to those dependencies
+ */
+ private void addExplicitDeps(
+ ImmutableSortedKeyListMultimap.Builder<Attribute, LabelAndConfiguration> result, Rule rule,
+ String attrName, Iterable<Label> labels, BuildConfiguration configuration) {
+ if (!rule.isAttrDefined(attrName, Type.LABEL_LIST)
+ && !rule.isAttrDefined(attrName, Type.NODEP_LABEL_LIST)) {
+ return;
+ }
+ Attribute attribute = rule.getRuleClassObject().getAttributeByName(attrName);
+ for (Label label : labels) {
+ // The configuration must be the configuration after the first transition step (applying
+ // split configurations). The proper configuration (null) for package groups will be set
+ // later.
+ result.put(attribute, LabelAndConfiguration.of(label, configuration));
+ }
+ }
+
+ private void resolveExplicitAttributes(Rule rule, final BuildConfiguration configuration,
+ AttributeMap attributes,
+ final ImmutableSortedKeyListMultimap.Builder<Attribute, LabelAndConfiguration> builder) {
+ attributes.visitLabels(
+ new AttributeMap.AcceptsLabelAttribute() {
+ @Override
+ public void acceptLabelAttribute(Label label, Attribute attribute) {
+ String attributeName = attribute.getName();
+ if (attributeName.equals("abi_deps")) {
+ // abi_deps is handled specially: we visit only the branch that
+ // needs to be taken based on the configuration.
+ return;
+ }
+
+ if (attribute.getType() == Type.NODEP_LABEL) {
+ return;
+ }
+
+ if (attribute.isImplicit() || attribute.isLateBound()) {
+ return;
+ }
+
+ builder.put(attribute, LabelAndConfiguration.of(label, configuration));
+ }
+ });
+
+ // TODO(bazel-team): Remove this in favor of the new configurable attributes.
+ if (attributes.getAttributeDefinition("abi_deps") != null) {
+ Attribute depsAttribute = attributes.getAttributeDefinition("deps");
+ MakeVariableExpander.Context context = new ConfigurationMakeVariableContext(
+ rule.getPackage(), configuration);
+ String abi = null;
+ try {
+ abi = MakeVariableExpander.expand(attributes.get("abi", Type.STRING), context);
+ } catch (MakeVariableExpander.ExpansionException e) {
+ // Ignore this. It will be handled during the analysis phase.
+ }
+
+ if (abi != null) {
+ for (Map.Entry<String, List<Label>> entry
+ : attributes.get("abi_deps", Type.LABEL_LIST_DICT).entrySet()) {
+ try {
+ if (Pattern.matches(entry.getKey(), abi)) {
+ for (Label label : entry.getValue()) {
+ builder.put(depsAttribute, LabelAndConfiguration.of(label, configuration));
+ }
+ }
+ } catch (PatternSyntaxException e) {
+ // Ignore this. It will be handled during the analysis phase.
+ }
+ }
+ }
+ }
+ }
+
+ private void resolveImplicitAttributes(Rule rule, BuildConfiguration configuration,
+ AttributeMap attributeMap, Iterable<Attribute> attributes,
+ ImmutableSortedKeyListMultimap.Builder<Attribute, LabelAndConfiguration> builder) {
+ // Since the attributes that come from aspects do not appear in attributeMap, we have to get
+ // their values from somewhere else. This incidentally means that aspects attributes are not
+ // configurable. It would be nice if that wasn't the case, but we'd have to revamp how
+ // attribute mapping works, which is a large chunk of work.
+ ImmutableSet<String> mappedAttributes = ImmutableSet.copyOf(attributeMap.getAttributeNames());
+ for (Attribute attribute : attributes) {
+ if (!attribute.isImplicit() || !attribute.getCondition().apply(attributeMap)) {
+ continue;
+ }
+
+ if (attribute.getType() == Type.LABEL) {
+ Label label = mappedAttributes.contains(attribute.getName())
+ ? attributeMap.get(attribute.getName(), Type.LABEL)
+ : Type.LABEL.cast(attribute.getDefaultValue(rule));
+
+ if (label != null) {
+ builder.put(attribute, LabelAndConfiguration.of(label, configuration));
+ }
+ } else if (attribute.getType() == Type.LABEL_LIST) {
+ List<Label> labelList = mappedAttributes.contains(attribute.getName())
+ ? attributeMap.get(attribute.getName(), Type.LABEL_LIST)
+ : Type.LABEL_LIST.cast(attribute.getDefaultValue(rule));
+
+ for (Label label : labelList) {
+ builder.put(attribute, LabelAndConfiguration.of(label, configuration));
+ }
+ }
+ }
+ }
+
+ private void resolveLateBoundAttributes(Rule rule, BuildConfiguration configuration,
+ AttributeMap attributeMap, Iterable<Attribute> attributes,
+ ImmutableSortedKeyListMultimap.Builder<Attribute, LabelAndConfiguration> builder)
+ throws EvalException {
+ for (Attribute attribute : attributes) {
+ if (!attribute.isLateBound() || !attribute.getCondition().apply(attributeMap)) {
+ continue;
+ }
+
+ List<BuildConfiguration> actualConfigurations = ImmutableList.of(configuration);
+ if (attribute.getConfigurationTransition() instanceof SplitTransition<?>) {
+ Preconditions.checkState(attribute.getConfigurator() == null);
+ // TODO(bazel-team): This ends up applying the split transition twice, both here and in the
+ // visitRule method below - this is not currently a problem, because the configuration graph
+ // never contains nested split transitions, so the second application is idempotent.
+ actualConfigurations = configuration.getSplitConfigurations(
+ (SplitTransition<?>) attribute.getConfigurationTransition());
+ }
+
+ for (BuildConfiguration actualConfig : actualConfigurations) {
+ @SuppressWarnings("unchecked")
+ LateBoundDefault<BuildConfiguration> lateBoundDefault =
+ (LateBoundDefault<BuildConfiguration>) attribute.getLateBoundDefault();
+ if (lateBoundDefault.useHostConfiguration()) {
+ actualConfig =
+ actualConfig.getConfiguration(ConfigurationTransition.HOST);
+ }
+ // TODO(bazel-team): This might be too expensive - can we cache this somehow?
+ if (!lateBoundDefault.getRequiredConfigurationFragments().isEmpty()) {
+ if (!actualConfig.hasAllFragments(lateBoundDefault.getRequiredConfigurationFragments())) {
+ continue;
+ }
+ }
+
+ // TODO(bazel-team): We should check if the implementation tries to access an undeclared
+ // fragment.
+ Object actualValue = lateBoundDefault.getDefault(rule, actualConfig);
+ if (attribute.getType() == Type.LABEL) {
+ Label label;
+ label = Type.LABEL.cast(actualValue);
+ if (label != null) {
+ builder.put(attribute, LabelAndConfiguration.of(label, actualConfig));
+ }
+ } else if (attribute.getType() == Type.LABEL_LIST) {
+ for (Label label : Type.LABEL_LIST.cast(actualValue)) {
+ builder.put(attribute, LabelAndConfiguration.of(label, actualConfig));
+ }
+ } else {
+ throw new IllegalStateException(String.format(
+ "Late bound attribute '%s' is not a label or a label list", attribute.getName()));
+ }
+ }
+ }
+ }
+
+ /**
+ * A variant of {@link #dependentNodeMap} that only returns the values of the resulting map, and
+ * also converts any internally thrown {@link EvalException} instances into {@link
+ * IllegalStateException}.
+ */
+ public final Collection<Dependency> dependentNodes(
+ TargetAndConfiguration node, Set<ConfigMatchingProvider> configConditions) {
+ try {
+ return dependentNodeMap(node, null, configConditions).values();
+ } catch (EvalException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Converts the given multimap of attributes to labels into a multi map of attributes to
+ * {@link Dependency} objects using the proper configuration transition for each attribute.
+ *
+ * @throws IllegalArgumentException if the {@code node} does not refer to a {@link Rule} instance
+ */
+ public final Collection<Dependency> resolveRuleLabels(
+ TargetAndConfiguration node, AspectDefinition aspect, ListMultimap<Attribute,
+ LabelAndConfiguration> labelMap) {
+ Preconditions.checkArgument(node.getTarget() instanceof Rule);
+ Rule rule = (Rule) node.getTarget();
+ ListMultimap<Attribute, Dependency> outgoingEdges = ArrayListMultimap.create();
+ visitRule(rule, aspect, labelMap, outgoingEdges);
+ return outgoingEdges.values();
+ }
+
+ private void visitPackageGroup(TargetAndConfiguration node, PackageGroup packageGroup,
+ Collection<Dependency> outgoingEdges) {
+ for (Label label : packageGroup.getIncludes()) {
+ try {
+ Target target = getTarget(label);
+ if (target == null) {
+ return;
+ }
+ if (!(target instanceof PackageGroup)) {
+ // Note that this error could also be caught in PackageGroupConfiguredTarget, but since
+ // these have the null configuration, visiting the corresponding target would trigger an
+ // analysis of a rule with a null configuration, which doesn't work.
+ invalidPackageGroupReferenceHook(node, label);
+ continue;
+ }
+
+ outgoingEdges.add(new Dependency(label, node.getConfiguration()));
+ } catch (NoSuchThingException e) {
+ // Don't visit targets that don't exist (--keep_going)
+ }
+ }
+ }
+
+ private ImmutableSet<Class<? extends ConfiguredAspectFactory>> requiredAspects(
+ AspectDefinition aspect, Attribute attribute, Target target) {
+ if (!(target instanceof Rule)) {
+ return ImmutableSet.of();
+ }
+
+ RuleClass ruleClass = ((Rule) target).getRuleClassObject();
+
+ // The order of this set will be deterministic. This is necessary because this order eventually
+ // influences the order in which aspects are merged into the main configured target, which in
+ // turn influences which aspect takes precedence if two emit the same provider (maybe this
+ // should be an error)
+ Set<Class<? extends AspectFactory<?, ?, ?>>> aspectCandidates = new LinkedHashSet<>();
+ aspectCandidates.addAll(attribute.getAspects());
+ if (aspect != null) {
+ aspectCandidates.addAll(aspect.getAttributeAspects().get(attribute.getName()));
+ }
+
+ ImmutableSet.Builder<Class<? extends ConfiguredAspectFactory>> result = ImmutableSet.builder();
+ for (Class<? extends AspectFactory<?, ?, ?>> candidateClass : aspectCandidates) {
+ ConfiguredAspectFactory candidate =
+ (ConfiguredAspectFactory) AspectFactory.Util.create(candidateClass);
+ if (Sets.difference(
+ candidate.getDefinition().getRequiredProviders(),
+ ruleClass.getAdvertisedProviders()).isEmpty()) {
+ result.add(candidateClass.asSubclass(ConfiguredAspectFactory.class));
+ }
+ }
+
+ return result.build();
+ }
+
+ private void visitRule(Rule rule, AspectDefinition aspect,
+ ListMultimap<Attribute, LabelAndConfiguration> labelMap,
+ ListMultimap<Attribute, Dependency> outgoingEdges) {
+ Preconditions.checkNotNull(labelMap);
+ for (Map.Entry<Attribute, Collection<LabelAndConfiguration>> entry :
+ labelMap.asMap().entrySet()) {
+ Attribute attribute = entry.getKey();
+ for (LabelAndConfiguration dep : entry.getValue()) {
+ Label label = dep.getLabel();
+ BuildConfiguration config = dep.getConfiguration();
+
+ Target toTarget;
+ try {
+ toTarget = getTarget(label);
+ } catch (NoSuchThingException e) {
+ throw new IllegalStateException("not found: " + label + " from " + rule + " in "
+ + attribute.getName());
+ }
+ if (toTarget == null) {
+ continue;
+ }
+ Iterable<BuildConfiguration> toConfigurations = config.evaluateTransition(
+ rule, attribute, toTarget);
+ for (BuildConfiguration toConfiguration : toConfigurations) {
+ outgoingEdges.get(entry.getKey()).add(new Dependency(
+ label, toConfiguration, requiredAspects(aspect, attribute, toTarget)));
+ }
+ }
+ }
+ }
+
+ private void visitTargetVisibility(TargetAndConfiguration node,
+ Collection<Dependency> outgoingEdges) {
+ for (Label label : node.getTarget().getVisibility().getDependencyLabels()) {
+ try {
+ Target visibilityTarget = getTarget(label);
+ if (visibilityTarget == null) {
+ return;
+ }
+ if (!(visibilityTarget instanceof PackageGroup)) {
+ // Note that this error could also be caught in
+ // AbstractConfiguredTarget.convertVisibility(), but we have an
+ // opportunity here to avoid dependency cycles that result from
+ // the visibility attribute of a rule referring to a rule that
+ // depends on it (instead of its package)
+ invalidVisibilityReferenceHook(node, label);
+ continue;
+ }
+
+ // Visibility always has null configuration
+ outgoingEdges.add(new Dependency(label, null));
+ } catch (NoSuchThingException e) {
+ // Don't visit targets that don't exist (--keep_going)
+ }
+ }
+ }
+
+ /**
+ * Hook for the error case when an invalid visibility reference is found.
+ *
+ * @param node the node with the visibility attribute
+ * @param label the invalid visibility reference
+ */
+ protected abstract void invalidVisibilityReferenceHook(TargetAndConfiguration node, Label label);
+
+ /**
+ * Hook for the error case when an invalid package group reference is found.
+ *
+ * @param node the package group node with the includes attribute
+ * @param label the invalid reference
+ */
+ protected abstract void invalidPackageGroupReferenceHook(TargetAndConfiguration node,
+ Label label);
+
+ /**
+ * Returns the target by the given label.
+ *
+ * <p>Throws {@link NoSuchThingException} if the target is known not to exist.
+ *
+ * <p>Returns null if the target is not ready to be returned at this moment. If getTarget returns
+ * null once or more during a {@link #dependentNodeMap} call, the results of that call will be
+ * incomplete. For use within Skyframe, where several iterations may be needed to discover
+ * all dependencies.
+ */
+ @Nullable
+ protected abstract Target getTarget(Label label) throws NoSuchThingException;
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ErrorConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/ErrorConfiguredTarget.java
new file mode 100644
index 0000000..aa07a7d
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ErrorConfiguredTarget.java
@@ -0,0 +1,41 @@
+// 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.collect.UnmodifiableIterator;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.packages.Target;
+
+/**
+ * A configured target that is used instead of a real configured target if there
+ * are cyclic dependencies or if any of the prerequisites has errors. This
+ * avoids accessing state that shouldn't be accessed.
+ */
+final class ErrorConfiguredTarget extends AbstractConfiguredTarget {
+ ErrorConfiguredTarget(Target target, BuildConfiguration configuration) {
+ super(target, configuration);
+ }
+
+ @Override
+ public Object get(String providerKey) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public UnmodifiableIterator<TransitiveInfoProvider> iterator() {
+ throw new IllegalStateException();
+ }
+}
+
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionArtifactsProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionArtifactsProvider.java
new file mode 100644
index 0000000..05b5f37
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionArtifactsProvider.java
@@ -0,0 +1,103 @@
+// 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.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+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.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.syntax.Label;
+
+/**
+ * A {@link TransitiveInfoProvider} that creates extra actions.
+ */
+@Immutable
+public final class ExtraActionArtifactsProvider implements TransitiveInfoProvider {
+ public static final ExtraActionArtifactsProvider EMPTY =
+ new ExtraActionArtifactsProvider(
+ ImmutableList.<Artifact>of(),
+ NestedSetBuilder.<ExtraArtifactSet>emptySet(Order.STABLE_ORDER));
+
+ /**
+ * The set of extra artifacts provided by a single configured target.
+ */
+ @Immutable
+ public static final class ExtraArtifactSet {
+ private final Label label;
+ private final ImmutableList<Artifact> artifacts;
+
+ private ExtraArtifactSet(Label label, Iterable<Artifact> artifacts) {
+ this.label = label;
+ this.artifacts = ImmutableList.copyOf(artifacts);
+ }
+
+ public Label getLabel() {
+ return label;
+ }
+
+ public ImmutableList<Artifact> getArtifacts() {
+ return artifacts;
+ }
+
+ public static ExtraArtifactSet of(Label label, Iterable<Artifact> artifacts) {
+ return new ExtraArtifactSet(label, artifacts);
+ }
+
+ @Override
+ public int hashCode() {
+ return label.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof ExtraArtifactSet)) {
+ return false;
+ }
+
+ return label.equals(((ExtraArtifactSet) other).getLabel());
+ }
+ }
+
+ /** The outputs of the extra actions associated with this target. */
+ private ImmutableList<Artifact> extraActionArtifacts = ImmutableList.of();
+ private NestedSet<ExtraArtifactSet> transitiveExtraActionArtifacts =
+ NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+
+ public ExtraActionArtifactsProvider(ImmutableList<Artifact> extraActionArtifacts,
+ NestedSet<ExtraArtifactSet> transitiveExtraActionArtifacts) {
+ this.extraActionArtifacts = extraActionArtifacts;
+ this.transitiveExtraActionArtifacts = transitiveExtraActionArtifacts;
+ }
+
+ /**
+ * The outputs of the extra actions associated with this target.
+ */
+ public ImmutableList<Artifact> getExtraActionArtifacts() {
+ return extraActionArtifacts;
+ }
+
+ /**
+ * The outputs of the extra actions in the whole transitive closure.
+ */
+ public NestedSet<ExtraArtifactSet> getTransitiveExtraActionArtifacts() {
+ return transitiveExtraActionArtifacts;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionsVisitor.java b/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionsVisitor.java
new file mode 100644
index 0000000..79ffe80
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionsVisitor.java
@@ -0,0 +1,84 @@
+// 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.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionGraph;
+import com.google.devtools.build.lib.actions.ActionGraphVisitor;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.rules.extra.ExtraActionSpec;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * A bipartite graph visitor which accumulates extra actions for a target.
+ */
+final class ExtraActionsVisitor extends ActionGraphVisitor {
+ private final RuleContext ruleContext;
+ private final Multimap<String, ExtraActionSpec> mnemonicToExtraActionMap;
+ private final List<Artifact> extraArtifacts;
+ public final Set<Action> actions = Sets.newHashSet();
+
+ /** Creates a new visitor for the extra actions associated with the given target. */
+ public ExtraActionsVisitor(RuleContext ruleContext,
+ Multimap<String, ExtraActionSpec> mnemonicToExtraActionMap) {
+ super(getActionGraph(ruleContext));
+ this.ruleContext = ruleContext;
+ this.mnemonicToExtraActionMap = mnemonicToExtraActionMap;
+ extraArtifacts = Lists.newArrayList();
+ }
+
+ public void addExtraAction(Action original) {
+ Collection<ExtraActionSpec> extraActions = mnemonicToExtraActionMap.get(
+ original.getMnemonic());
+ if (extraActions != null) {
+ for (ExtraActionSpec extraAction : extraActions) {
+ extraArtifacts.addAll(extraAction.addExtraAction(ruleContext, original));
+ }
+ }
+ }
+
+ @Override
+ protected void visitAction(Action action) {
+ actions.add(action);
+ addExtraAction(action);
+ }
+
+ /** Retrieves the collected artifacts since this method was last called and clears the list. */
+ public ImmutableList<Artifact> getAndResetExtraArtifacts() {
+ ImmutableList<Artifact> collected = ImmutableList.copyOf(extraArtifacts);
+ extraArtifacts.clear();
+ return collected;
+ }
+
+ /** Gets an action graph wrapper for the given target through its analysis environment. */
+ private static ActionGraph getActionGraph(final RuleContext ruleContext) {
+ return new ActionGraph() {
+ @Override
+ @Nullable
+ public Action getGeneratingAction(Artifact artifact) {
+ return ruleContext.getAnalysisEnvironment().getLocalGeneratingAction(artifact);
+ }
+ };
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/FileConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/FileConfiguredTarget.java
new file mode 100644
index 0000000..815eea7
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/FileConfiguredTarget.java
@@ -0,0 +1,93 @@
+// 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.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.UnmodifiableIterator;
+import com.google.devtools.build.lib.actions.Artifact;
+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.packages.FileTarget;
+import com.google.devtools.build.lib.rules.fileset.FilesetProvider;
+import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider;
+import com.google.devtools.build.lib.util.FileType;
+
+/**
+ * A ConfiguredTarget for a source FileTarget. (Generated files use a
+ * subclass, OutputFileConfiguredTarget.)
+ */
+public abstract class FileConfiguredTarget extends AbstractConfiguredTarget
+ implements FileType.HasFilename, LicensesProvider {
+
+ private final Artifact artifact;
+ private final ImmutableMap<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider>
+ providers;
+
+ FileConfiguredTarget(TargetContext targetContext, Artifact artifact) {
+ super(targetContext);
+ NestedSet<Artifact> filesToBuild = NestedSetBuilder.create(Order.STABLE_ORDER, artifact);
+ this.artifact = artifact;
+ Builder<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> builder = ImmutableMap
+ .<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider>builder()
+ .put(VisibilityProvider.class, this)
+ .put(LicensesProvider.class, this)
+ .put(FileProvider.class, new FileProvider(targetContext.getLabel(), filesToBuild))
+ .put(FilesToRunProvider.class, new FilesToRunProvider(targetContext.getLabel(),
+ ImmutableList.copyOf(filesToBuild), null, artifact));
+ if (this instanceof FilesetProvider) {
+ builder.put(FilesetProvider.class, this);
+ }
+ if (this instanceof InstrumentedFilesProvider) {
+ builder.put(InstrumentedFilesProvider.class, this);
+ }
+ this.providers = builder.build();
+ }
+
+ @Override
+ public FileTarget getTarget() {
+ return (FileTarget) super.getTarget();
+ }
+
+ public Artifact getArtifact() {
+ return artifact;
+ }
+
+ /**
+ * Returns the file type of this file target.
+ */
+ @Override
+ public String getFilename() {
+ return getTarget().getFilename();
+ }
+
+ @Override
+ public <P extends TransitiveInfoProvider> P getProvider(Class<P> provider) {
+ AnalysisUtils.checkProvider(provider);
+ return provider.cast(providers.get(provider));
+ }
+
+ @Override
+ public Object get(String providerKey) {
+ return null;
+ }
+
+ @Override
+ public UnmodifiableIterator<TransitiveInfoProvider> iterator() {
+ return providers.values().iterator();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/FileProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/FileProvider.java
new file mode 100644
index 0000000..893f211
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/FileProvider.java
@@ -0,0 +1,76 @@
+// 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.SkylarkCallable;
+import com.google.devtools.build.lib.syntax.SkylarkModule;
+
+import javax.annotation.Nullable;
+
+/**
+ * A representation of the concept "this transitive info provider builds these files".
+ *
+ * <p>Every transitive info collection contains at least this provider.
+ */
+@Immutable
+@SkylarkModule(name = "file_provider", doc = "An interface for rules that provide files.")
+public final class FileProvider implements TransitiveInfoProvider {
+
+ @Nullable private final Label label;
+ private final NestedSet<Artifact> filesToBuild;
+
+ public FileProvider(@Nullable Label label, NestedSet<Artifact> filesToBuild) {
+ this.label = label;
+ this.filesToBuild = filesToBuild;
+ }
+
+ /**
+ * Returns the label that is associated with this piece of information.
+ *
+ * <p>This is usually the label of the target that provides the information.
+ */
+ @SkylarkCallable(name = "label", doc = "", structField = true)
+ public Label getLabel() {
+ if (label == null) {
+ throw new UnsupportedOperationException();
+ }
+ return label;
+ }
+
+ /**
+ * Returns the set of artifacts that are the "output" of this rule.
+ *
+ * <p>The term "output" is somewhat hazily defined; it is vaguely the set of files that are
+ * passed on to dependent rules that list the rule in their {@code srcs} attribute and the
+ * set of files that are built when a rule is mentioned on the command line. It does
+ * <b>not</b> include the runfiles; that is the bailiwick of {@code FilesToRunProvider}.
+ *
+ * <p>Note that the above definition is somewhat imprecise; in particular, when a rule is
+ * mentioned on the command line, some other files are also built
+ * {@code TopLevelArtifactHelper} and dependent rules are free to filter this set of artifacts
+ * e.g. based on their extension.
+ *
+ * <p>Also, some rules may generate artifacts that are not listed here by way of defining other
+ * implicit targets, for example, deploy jars.
+ */
+ @SkylarkCallable(name = "files_to_build", doc = "", structField = true)
+ public NestedSet<Artifact> getFilesToBuild() {
+ return filesToBuild;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/FilesToCompileProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/FilesToCompileProvider.java
new file mode 100644
index 0000000..025392c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/FilesToCompileProvider.java
@@ -0,0 +1,42 @@
+// 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.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+/**
+ * A {@link TransitiveInfoProvider} that provides files to be built when the {@code --compile_only}
+ * command line option is in effect. This is to avoid expensive build steps when the user only
+ * wants a quick syntax check.
+ */
+@Immutable
+public final class FilesToCompileProvider implements TransitiveInfoProvider {
+
+ private final ImmutableList<Artifact> filesToCompile;
+
+ public FilesToCompileProvider(ImmutableList<Artifact> filesToCompile) {
+ this.filesToCompile = filesToCompile;
+ }
+
+ /**
+ * Returns the list of artifacts to be built when the {@code --compile_only} command line option
+ * is in effect.
+ */
+ public ImmutableList<Artifact> getFilesToCompile() {
+ return filesToCompile;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/FilesToRunProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/FilesToRunProvider.java
new file mode 100644
index 0000000..0e024b1
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/FilesToRunProvider.java
@@ -0,0 +1,80 @@
+// 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.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.syntax.Label;
+
+import javax.annotation.Nullable;
+
+/**
+ * Returns information about executables produced by a target and the files needed to run it.
+ */
+@Immutable
+public final class FilesToRunProvider implements TransitiveInfoProvider {
+
+ private final Label label;
+ private final ImmutableList<Artifact> filesToRun;
+ @Nullable private final RunfilesSupport runfilesSupport;
+ @Nullable private final Artifact executable;
+
+ public FilesToRunProvider(Label label, ImmutableList<Artifact> filesToRun,
+ @Nullable RunfilesSupport runfilesSupport, @Nullable Artifact executable) {
+ this.label = label;
+ this.filesToRun = filesToRun;
+ this.runfilesSupport = runfilesSupport;
+ this.executable = executable;
+ }
+
+ /**
+ * Returns the label that is associated with this piece of information.
+ *
+ * <p>This is usually the label of the target that provides the information.
+ */
+ public Label getLabel() {
+ return label;
+ }
+
+ /**
+ * Returns artifacts needed to run the executable for this target.
+ */
+ public ImmutableList<Artifact> getFilesToRun() {
+ return filesToRun;
+ }
+
+ /**
+ * Returns the {@RunfilesSupport} object associated with the target or null if it does not exist.
+ */
+ @Nullable public RunfilesSupport getRunfilesSupport() {
+ return runfilesSupport;
+ }
+
+ /**
+ * Returns the Executable or null if it does not exist.
+ */
+ @Nullable public Artifact getExecutable() {
+ return executable;
+ }
+
+ /**
+ * Returns the RunfilesManifest or null if it does not exist. It is a shortcut to
+ * getRunfilesSupport().getRunfilesManifest().
+ */
+ @Nullable public Artifact getRunfilesManifest() {
+ return runfilesSupport != null ? runfilesSupport.getRunfilesManifest() : null;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/FilesetOutputConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/FilesetOutputConfiguredTarget.java
new file mode 100644
index 0000000..860024d
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/FilesetOutputConfiguredTarget.java
@@ -0,0 +1,55 @@
+// 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.packages.OutputFile;
+import com.google.devtools.build.lib.rules.fileset.FilesetProvider;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+/**
+ * A configured target for output files generated by {@code Fileset} rules. They are almost the
+ * same thing as output files except that they implement {@link FilesetProvider} so that
+ * {@code Fileset} can figure out the link tree behind them.
+ *
+ * <p>In an ideal world, this would not be needed: Filesets would depend on other Filesets and not
+ * their output directories. However, sometimes a Fileset depends on the output directory of
+ * another Fileset. Thus, we need this hack.
+ */
+public final class FilesetOutputConfiguredTarget extends OutputFileConfiguredTarget
+ implements FilesetProvider {
+ private final Artifact filesetInputManifest;
+ private final PathFragment filesetLinkDir;
+
+ FilesetOutputConfiguredTarget(TargetContext targetContext, OutputFile outputFile,
+ TransitiveInfoCollection generatingRule, Artifact outputArtifact) {
+ super(targetContext, outputFile, generatingRule, outputArtifact);
+ FilesetProvider provider = generatingRule.getProvider(FilesetProvider.class);
+ Preconditions.checkArgument(provider != null);
+ filesetInputManifest = provider.getFilesetInputManifest();
+ filesetLinkDir = provider.getFilesetLinkDir();
+ }
+
+ @Override
+ public Artifact getFilesetInputManifest() {
+ return filesetInputManifest;
+ }
+
+ @Override
+ public PathFragment getFilesetLinkDir() {
+ return filesetLinkDir;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/InputFileConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/InputFileConfiguredTarget.java
new file mode 100644
index 0000000..9e56033
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/InputFileConfiguredTarget.java
@@ -0,0 +1,68 @@
+// 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.devtools.build.lib.actions.Artifact;
+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.packages.InputFile;
+import com.google.devtools.build.lib.packages.License;
+
+/**
+ * A ConfiguredTarget for an InputFile.
+ *
+ * All InputFiles for the same target are equivalent, so configuration does not
+ * play any role here and is always set to <b>null</b>.
+ */
+public final class InputFileConfiguredTarget extends FileConfiguredTarget {
+ private final Artifact artifact;
+ private final NestedSet<TargetLicense> licenses;
+
+ InputFileConfiguredTarget(TargetContext targetContext, InputFile inputFile, Artifact artifact) {
+ super(targetContext, artifact);
+ Preconditions.checkArgument(targetContext.getTarget() == inputFile, getLabel());
+ Preconditions.checkArgument(getConfiguration() == null, getLabel());
+ this.artifact = artifact;
+
+ if (inputFile.getLicense() != License.NO_LICENSE) {
+ licenses = NestedSetBuilder.create(Order.LINK_ORDER,
+ new TargetLicense(getLabel(), inputFile.getLicense()));
+ } else {
+ licenses = NestedSetBuilder.emptySet(Order.LINK_ORDER);
+ }
+ }
+
+ @Override
+ public InputFile getTarget() {
+ return (InputFile) super.getTarget();
+ }
+
+ @Override
+ public Artifact getArtifact() {
+ return artifact;
+ }
+
+ @Override
+ public String toString() {
+ return "InputFileConfiguredTarget(" + getTarget().getLabel() + ")";
+ }
+
+ @Override
+ public final NestedSet<TargetLicense> getTransitiveLicenses() {
+ return licenses;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/LabelAndConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/LabelAndConfiguration.java
new file mode 100644
index 0000000..66efba3
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/LabelAndConfiguration.java
@@ -0,0 +1,76 @@
+// 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.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.syntax.Label;
+
+import java.util.Objects;
+
+import javax.annotation.Nullable;
+
+/**
+* A (label,configuration) pair.
+*/
+public final class LabelAndConfiguration {
+ private final Label label;
+ @Nullable
+ private final BuildConfiguration configuration;
+
+ private LabelAndConfiguration(Label label, @Nullable BuildConfiguration configuration) {
+ this.label = Preconditions.checkNotNull(label);
+ this.configuration = configuration;
+ }
+
+ public LabelAndConfiguration(ConfiguredTarget rule) {
+ this(rule.getTarget().getLabel(), rule.getConfiguration());
+ }
+
+ public Label getLabel() {
+ return label;
+ }
+
+ @Nullable
+ public BuildConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ @Override
+ public int hashCode() {
+ int configVal = configuration == null ? 79 : configuration.hashCode();
+ return 31 * label.hashCode() + configVal;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof LabelAndConfiguration)) {
+ return false;
+ }
+ LabelAndConfiguration other = (LabelAndConfiguration) obj;
+ return Objects.equals(label, other.label) && Objects.equals(configuration, other.configuration);
+ }
+
+ public static LabelAndConfiguration of(
+ Label label, @Nullable BuildConfiguration configuration) {
+ return new LabelAndConfiguration(label, configuration);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/LabelExpander.java b/src/main/java/com/google/devtools/build/lib/analysis/LabelExpander.java
new file mode 100644
index 0000000..89ce2e7
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/LabelExpander.java
@@ -0,0 +1,181 @@
+// 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.CharMatcher;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/**
+ * Helper class encapsulating string scanning state used during "heuristic"
+ * expansion of labels embedded within rules.
+ */
+public final class LabelExpander {
+ /**
+ * An exception that is thrown when a label is expanded to zero or multiple
+ * files during expansion.
+ */
+ public static class NotUniqueExpansionException extends Exception {
+ public NotUniqueExpansionException(int sizeOfResultSet, String labelText) {
+ super("heuristic label expansion found '" + labelText + "', which expands to "
+ + sizeOfResultSet + " files"
+ + (sizeOfResultSet > 1
+ ? ", please use $(locations " + labelText + ") instead"
+ : ""));
+ }
+ }
+
+ // This is a utility class, no need to instantiate.
+ private LabelExpander() {}
+
+ /**
+ * CharMatcher to determine if a given character is valid for labels.
+ *
+ * <p>The Build Concept Reference additionally allows '=' and ',' to appear in labels,
+ * but for the purposes of the heuristic, this function does not, as it would cause
+ * "--foo=:rule1,:rule2" to scan as a single possible label, instead of three
+ * ("--foo", ":rule1", ":rule2").
+ */
+ private static final CharMatcher LABEL_CHAR_MATCHER =
+ CharMatcher.inRange('a', 'z')
+ .or(CharMatcher.inRange('A', 'Z'))
+ .or(CharMatcher.inRange('0', '9'))
+ .or(CharMatcher.anyOf(":/_.-+" + PathFragment.SEPARATOR_CHAR));
+
+ /**
+ * Expands all references to labels embedded within a string using the
+ * provided expansion mapping from labels to artifacts.
+ *
+ * <p>Since this pass is heuristic, references to non-existent labels (such
+ * as arbitrary words) or invalid labels are simply ignored and are unchanged
+ * in the output. However, if the heuristic discovers a label, which
+ * identifies an existing target producing zero or multiple files, an error
+ * is reported.
+ *
+ * @param expression the expression to expand.
+ * @param labelMap the mapping from labels to artifacts, whose relative path
+ * is to be used as the expansion.
+ * @param labelResolver the {@code Label} that can resolve label strings
+ * to {@code Label} objects. The resolved label is either relative to
+ * {@code labelResolver} or is a global label (i.e. starts with "//").
+ * @return the expansion of the string.
+ * @throws NotUniqueExpansionException if a label that is present in the
+ * mapping expands to zero or multiple files.
+ */
+ public static <T extends Iterable<Artifact>> String expand(@Nullable String expression,
+ Map<Label, T> labelMap, Label labelResolver) throws NotUniqueExpansionException {
+ if (Strings.isNullOrEmpty(expression)) {
+ return "";
+ }
+ Preconditions.checkNotNull(labelMap);
+ Preconditions.checkNotNull(labelResolver);
+
+ int offset = 0;
+ StringBuilder result = new StringBuilder();
+ while (offset < expression.length()) {
+ String labelText = scanLabel(expression, offset);
+ if (labelText != null) {
+ offset += labelText.length();
+ result.append(tryResolvingLabelTextToArtifactPath(labelText, labelMap, labelResolver));
+ } else {
+ result.append(expression.charAt(offset));
+ offset++;
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * Tries resolving a label text to a full label for the associated {@code
+ * Artifact}, using the provided mapping.
+ *
+ * <p>The method succeeds if the label text can be resolved to a {@code
+ * Label} object, which is present in the {@code labelMap} and maps to
+ * exactly one {@code Artifact}.
+ *
+ * @param labelText the text to resolve.
+ * @param labelMap the mapping from labels to artifacts, whose relative path
+ * is to be used as the expansion.
+ * @param labelResolver the {@code Label} that can resolve label strings
+ * to {@code Label} objects. The resolved label is either relative to
+ * {@code labelResolver} or is a global label (i.e. starts with "//").
+ * @return an absolute label to an {@code Artifact} if the resolving was
+ * successful or the original label text.
+ * @throws NotUniqueExpansionException if a label that is present in the
+ * mapping expands to zero or multiple files.
+ */
+ private static <T extends Iterable<Artifact>> String tryResolvingLabelTextToArtifactPath(
+ String labelText, Map<Label, T> labelMap, Label labelResolver)
+ throws NotUniqueExpansionException {
+ Label resolvedLabel = resolveLabelText(labelText, labelResolver);
+ if (resolvedLabel != null) {
+ Iterable<Artifact> artifacts = labelMap.get(resolvedLabel);
+ if (artifacts != null) { // resolvedLabel identifies an existing target
+ List<String> locations = new ArrayList<>();
+ Artifact.addExecPaths(artifacts, locations);
+ int resultSetSize = locations.size();
+ if (resultSetSize == 1) {
+ return Iterables.getOnlyElement(locations); // success!
+ } else {
+ throw new NotUniqueExpansionException(resultSetSize, labelText);
+ }
+ }
+ }
+ return labelText;
+ }
+
+ /**
+ * Resolves a string to a label text. Uses {@code labelResolver} to do so.
+ * The result is either relative to {@code labelResolver} or is an absolute
+ * label. In case of an invalid label text, the return value is null.
+ */
+ private static Label resolveLabelText(String labelText, Label labelResolver) {
+ try {
+ return labelResolver.getRelative(labelText);
+ } catch (Label.SyntaxException e) {
+ // It's a heuristic, so quietly ignore "errors". Because Label.getRelative never
+ // returns null, we can use null to indicate an error.
+ return null;
+ }
+ }
+
+ /**
+ * Scans the argument string from a given start position until the name of a
+ * potential label has been consumed, then returns the label text. If
+ * the expression contains no possible label starting at the start position,
+ * the return value is null.
+ */
+ private static String scanLabel(String expression, int start) {
+ int offset = start;
+ while (offset < expression.length() && LABEL_CHAR_MATCHER.matches(expression.charAt(offset))) {
+ ++offset;
+ }
+ if (offset > start) {
+ return expression.substring(start, offset);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/LanguageDependentFragment.java b/src/main/java/com/google/devtools/build/lib/analysis/LanguageDependentFragment.java
new file mode 100644
index 0000000..d42d625
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/LanguageDependentFragment.java
@@ -0,0 +1,109 @@
+// 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.collect.ImmutableSet;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.syntax.Label;
+
+import java.util.Set;
+
+/**
+ * Transitive info provider for rules that behave differently when used from
+ * different languages.
+ *
+ * <p>Most rules generate code for a particular language or are totally language independent.
+ * Some rules, however, behave differently when depended upon from different languages.
+ * They might generate different libraries when used from different languages (and with
+ * different API versions). This interface allows code sharing between implementations.
+ *
+ * <p>This provider is not really a roll-up of transitive information.
+ */
+@Immutable
+public final class LanguageDependentFragment implements TransitiveInfoProvider {
+ /**
+ * A language that can be supported by a multi-language configured target.
+ *
+ * <p>Note that no {@code hashCode}/{@code equals} methods are provided, because these
+ * objects are expected to be compared for object identity, which is the default.
+ */
+ public static final class LibraryLanguage {
+ private final String displayName;
+
+ public LibraryLanguage(String displayName) {
+ this.displayName = displayName;
+ }
+
+ @Override
+ public String toString() {
+ return displayName;
+ }
+ }
+
+ private final Label label;
+ private final ImmutableSet<LibraryLanguage> languages;
+
+ public LanguageDependentFragment(Label label, Set<LibraryLanguage> languages) {
+ this.label = label;
+ this.languages = ImmutableSet.copyOf(languages);
+ }
+
+ /**
+ * Returns the label that is associated with this piece of information.
+ *
+ * <p>This is usually the label of the target that provides the information.
+ */
+ public Label getLabel() {
+ return label;
+ }
+
+ /**
+ * Returns a set of the languages the ConfiguredTarget generates output for.
+ * For use only by rules that directly depend on this library via a "deps" attribute.
+ */
+ public ImmutableSet<LibraryLanguage> getSupportedLanguages() {
+ return languages;
+ }
+
+ /**
+ * Routines for verifying that dependency provide the right output.
+ */
+ public static final class Checker {
+ /**
+ * Checks that given dep supports the given language.
+ */
+ public static boolean depSupportsLanguage(
+ RuleContext context, LanguageDependentFragment dep, LibraryLanguage language) {
+ if (dep.getSupportedLanguages().contains(language)) {
+ return true;
+ } else {
+ context.attributeError(
+ "deps", String.format("'%s' does not produce output for %s", dep.getLabel(), language));
+ return false;
+ }
+ }
+
+ /**
+ * Checks that all LanguageDependentFragment support the given language.
+ */
+ public static void depsSupportsLanguage(RuleContext context, LibraryLanguage language) {
+ for (LanguageDependentFragment dep :
+ context.getPrerequisites("deps", Mode.TARGET, LanguageDependentFragment.class)) {
+ depSupportsLanguage(context, dep, language);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/LicensesProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/LicensesProvider.java
new file mode 100644
index 0000000..548a1f2
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/LicensesProvider.java
@@ -0,0 +1,88 @@
+// 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.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.packages.License;
+import com.google.devtools.build.lib.syntax.Label;
+
+import java.util.Objects;
+
+/**
+ * A {@link ConfiguredTarget} that has licensed targets in its transitive closure.
+ */
+public interface LicensesProvider extends TransitiveInfoProvider {
+
+ /**
+ * The set of label - license associations in the transitive closure.
+ *
+ * <p>Always returns an empty set if {@link BuildConfiguration#checkLicenses()} is false.
+ */
+ NestedSet<TargetLicense> getTransitiveLicenses();
+
+ /**
+ * License association for a particular target.
+ */
+ public static final class TargetLicense {
+
+ private final Label label;
+ private final License license;
+
+ public TargetLicense(Label label, License license) {
+ Preconditions.checkNotNull(label);
+ Preconditions.checkNotNull(license);
+ this.label = label;
+ this.license = license;
+ }
+
+ /**
+ * Returns the label of the associated target.
+ */
+ public Label getLabel() {
+ return label;
+ }
+
+ /**
+ * Returns the license for the target.
+ */
+ public License getLicense() {
+ return license;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(label, license);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof TargetLicense)) {
+ return false;
+ }
+ TargetLicense other = (TargetLicense) obj;
+ return label.equals(other.label) && license.equals(other.license);
+ }
+
+ @Override
+ public String toString() {
+ return label + " => " + license;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/LicensesProviderImpl.java b/src/main/java/com/google/devtools/build/lib/analysis/LicensesProviderImpl.java
new file mode 100644
index 0000000..ffdf9fd
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/LicensesProviderImpl.java
@@ -0,0 +1,40 @@
+// 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.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.concurrent.ThreadSafety.Immutable;
+
+/**
+ * A {@link ConfiguredTarget} that has licensed targets in its transitive closure.
+ */
+@Immutable
+public final class LicensesProviderImpl implements LicensesProvider {
+ public static final LicensesProvider EMPTY =
+ new LicensesProviderImpl(NestedSetBuilder.<TargetLicense>emptySet(Order.LINK_ORDER));
+
+ private final NestedSet<TargetLicense> transitiveLicenses;
+
+ public LicensesProviderImpl(NestedSet<TargetLicense> transitiveLicenses) {
+ this.transitiveLicenses = transitiveLicenses;
+ }
+
+ @Override
+ public NestedSet<TargetLicense> getTransitiveLicenses() {
+ return transitiveLicenses;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/LocationExpander.java b/src/main/java/com/google/devtools/build/lib/analysis/LocationExpander.java
new file mode 100644
index 0000000..8feb28e
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/LocationExpander.java
@@ -0,0 +1,260 @@
+// 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.Joiner;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.packages.OutputFile;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Expands $(location) tags inside target attributes.
+ * You can specify something like this in the BUILD file:
+ *
+ * somerule(name='some name',
+ * someopt = [ '$(location //mypackage:myhelper)' ],
+ * ...)
+ *
+ * and location will be substituted with //mypackage:myhelper executable output.
+ * Note that //mypackage:myhelper should have just one output.
+ */
+public class LocationExpander {
+ private static final int MAX_PATHS_SHOWN = 5;
+ private static final String LOCATION = "$(location";
+ private final RuleContext ruleContext;
+ private Map<Label, Collection<Artifact>> locationMap;
+ private boolean allowDataAttributeEntriesInLabel = false;
+
+ /**
+ * Creates location expander helper bound to specific target and with default
+ * location map.
+ *
+ * @param ruleContext BUILD rule
+ */
+ public LocationExpander(RuleContext ruleContext) {
+ this(ruleContext, false);
+ }
+
+ public LocationExpander(RuleContext ruleContext,
+ boolean allowDataAttributeEntriesInLabel) {
+ this.ruleContext = ruleContext;
+ this.allowDataAttributeEntriesInLabel = allowDataAttributeEntriesInLabel;
+ }
+
+ public Map<Label, Collection<Artifact>> getLocationMap() {
+ if (locationMap == null) {
+ locationMap = buildLocationMap(ruleContext, allowDataAttributeEntriesInLabel);
+ }
+ return locationMap;
+ }
+
+ /**
+ * Expands attribute's location and locations tags based on the target and
+ * location map.
+ *
+ * @param attrName name of the attribute
+ * @param attrValue initial value of the attribute
+ * @return attribute value with expanded location tags or original value in
+ * case of errors
+ */
+ public String expand(String attrName, String attrValue) {
+ int restart = 0;
+
+ int attrLength = attrValue.length();
+ StringBuilder result = new StringBuilder(attrValue.length());
+
+ while (true) {
+ // (1) find '$(location ' or '$(locations '
+ String message = "$(location)";
+ boolean multiple = false;
+ int start = attrValue.indexOf(LOCATION, restart);
+ int scannedLength = LOCATION.length();
+ if (start == -1 || start + scannedLength == attrLength) {
+ result.append(attrValue.substring(restart));
+ break;
+ }
+
+ if (attrValue.charAt(start + scannedLength) == 's') {
+ scannedLength++;
+ if (start + scannedLength == attrLength) {
+ result.append(attrValue.substring(restart));
+ break;
+ }
+ message = "$(locations)";
+ multiple = true;
+ }
+
+ if (attrValue.charAt(start + scannedLength) != ' ') {
+ result.append(attrValue.substring(restart, start + scannedLength));
+ restart = start + scannedLength;
+ continue;
+ }
+ scannedLength++;
+
+ int end = attrValue.indexOf(')', start + scannedLength);
+ if (end == -1) {
+ ruleContext.attributeError(attrName, "unterminated " + message + " expression");
+ return attrValue;
+ }
+
+ // (2) parse label
+ String labelText = attrValue.substring(start + scannedLength, end);
+ Label label;
+ try {
+ label = ruleContext.getLabel().getRelative(labelText);
+ } catch (Label.SyntaxException e) {
+ ruleContext.attributeError(attrName,
+ "invalid label in " + message + " expression: " + e.getMessage());
+ return attrValue;
+ }
+
+ // (3) replace with singleton artifact, iff unique.
+ Collection<Artifact> artifacts = getLocationMap().get(label);
+ if (artifacts == null) {
+ ruleContext.attributeError(attrName,
+ "label '" + label + "' in " + message + " expression is not a "
+ + "declared prerequisite of this rule");
+ return attrValue;
+ }
+ List<String> paths = getPaths(artifacts);
+ if (paths.isEmpty()) {
+ ruleContext.attributeError(attrName,
+ "label '" + label + "' in " + message + " expression expands to no "
+ + "files");
+ return attrValue;
+ }
+
+ result.append(attrValue.substring(restart, start));
+ if (multiple) {
+ Collections.sort(paths);
+ Joiner.on(' ').appendTo(result, paths);
+ } else {
+ if (paths.size() > 1) {
+ ruleContext.attributeError(attrName,
+ String.format(
+ "label '%s' in %s expression expands to more than one file, "
+ + "please use $(locations %s) instead. Files (at most %d shown) are: %s",
+ label, message, label,
+ MAX_PATHS_SHOWN, Iterables.limit(paths, MAX_PATHS_SHOWN)));
+ return attrValue;
+ }
+ result.append(Iterables.getOnlyElement(paths));
+ }
+ restart = end + 1;
+ }
+ return result.toString();
+ }
+
+ /**
+ * Extracts all possible target locations from target specification.
+ *
+ * @param ruleContext BUILD target object
+ * @return map of all possible target locations
+ */
+ private static Map<Label, Collection<Artifact>> buildLocationMap(RuleContext ruleContext,
+ boolean allowDataAttributeEntriesInLabel) {
+ Map<Label, Collection<Artifact>> locationMap = new HashMap<>();
+
+ // Add all destination locations.
+ for (OutputFile out : ruleContext.getRule().getOutputFiles()) {
+ mapGet(locationMap, out.getLabel()).add(ruleContext.createOutputArtifact(out));
+ }
+
+ if (ruleContext.getRule().isAttrDefined("srcs", Type.LABEL_LIST)) {
+ for (FileProvider src : ruleContext
+ .getPrerequisites("srcs", Mode.TARGET, FileProvider.class)) {
+ Iterables.addAll(mapGet(locationMap, src.getLabel()), src.getFilesToBuild());
+ }
+ }
+
+ // Add all locations associated with dependencies and tools
+ List<FilesToRunProvider> depsDataAndTools = new ArrayList<>();
+ if (ruleContext.getRule().isAttrDefined("deps", Type.LABEL_LIST)) {
+ Iterables.addAll(depsDataAndTools,
+ ruleContext.getPrerequisites("deps", Mode.DONT_CHECK, FilesToRunProvider.class));
+ }
+ if (allowDataAttributeEntriesInLabel
+ && ruleContext.getRule().isAttrDefined("data", Type.LABEL_LIST)) {
+ Iterables.addAll(depsDataAndTools,
+ ruleContext.getPrerequisites("data", Mode.DATA, FilesToRunProvider.class));
+ }
+ if (ruleContext.getRule().isAttrDefined("tools", Type.LABEL_LIST)) {
+ Iterables.addAll(depsDataAndTools,
+ ruleContext.getPrerequisites("tools", Mode.HOST, FilesToRunProvider.class));
+ }
+
+ for (FilesToRunProvider dep : depsDataAndTools) {
+ Label label = dep.getLabel();
+ Artifact executableArtifact = dep.getExecutable();
+
+ // If the label has an executable artifact add that to the multimaps.
+ if (executableArtifact != null) {
+ mapGet(locationMap, label).add(executableArtifact);
+ } else {
+ mapGet(locationMap, label).addAll(dep.getFilesToRun());
+ }
+ }
+ return locationMap;
+ }
+
+ /**
+ * Extracts list of all executables associated with given collection of label
+ * artifacts.
+ *
+ * @param artifacts to get the paths of
+ * @return all associated executable paths
+ */
+ private static List<String> getPaths(Collection<Artifact> artifacts) {
+ List<String> paths = Lists.newArrayListWithCapacity(artifacts.size());
+ for (Artifact artifact : artifacts) {
+ PathFragment execPath = artifact.getExecPath();
+ if (execPath != null) { // omit middlemen etc
+ paths.add(execPath.getPathString());
+ }
+ }
+ return paths;
+ }
+
+ /**
+ * Returns the value in the specified map corresponding to 'key', creating and
+ * inserting an empty container if absent. We use Map not Multimap because
+ * we need to distinguish the cases of "empty value" and "absent key".
+ *
+ * @return the value in the specified map corresponding to 'key'
+ */
+ private static <K, V> Collection<V> mapGet(Map<K, Collection<V>> map, K key) {
+ Collection<V> values = map.get(key);
+ if (values == null) {
+ // We use sets not lists, because it's conceivable that the same label
+ // could appear twice, in "srcs" and "deps".
+ values = Sets.newHashSet();
+ map.put(key, values);
+ }
+ return values;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/MakeEnvironmentEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/MakeEnvironmentEvent.java
new file mode 100644
index 0000000..f4b9ca8
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/MakeEnvironmentEvent.java
@@ -0,0 +1,40 @@
+// 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.collect.ImmutableMap;
+
+import java.util.Map;
+
+/**
+ * This event is fired once the global make environment is available.
+ */
+public final class MakeEnvironmentEvent {
+
+ private final Map<String, String> makeEnvMap;
+
+ /**
+ * Construct the event.
+ */
+ public MakeEnvironmentEvent(Map<String, String> makeEnv) {
+ makeEnvMap = ImmutableMap.copyOf(makeEnv);
+ }
+
+ /**
+ * Returns make environment variable names and values as a map.
+ */
+ public Map<String, String> getMakeEnvMap() {
+ return makeEnvMap;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/MakeVariableExpander.java b/src/main/java/com/google/devtools/build/lib/analysis/MakeVariableExpander.java
new file mode 100644
index 0000000..55366da
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/MakeVariableExpander.java
@@ -0,0 +1,201 @@
+// 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;
+
+/**
+ * MakeVariableExpander defines a utility method, <code>expand</code>, for
+ * expanding references to "Make" variables embedded within a string. The
+ * caller provides a Context instance which defines the expansion of each
+ * variable.
+ *
+ * <p>Note that neither <code>$(location x)</code> nor Make-isms are treated
+ * specially in any way by this class.
+ */
+public class MakeVariableExpander {
+
+ private final char[] buffer;
+ private final int length;
+ private int offset;
+
+ private MakeVariableExpander(String expression) {
+ buffer = expression.toCharArray();
+ length = buffer.length;
+ offset = 0;
+ }
+
+ /**
+ * Interface to be implemented by callers of MakeVariableExpander which
+ * defines the expansion of each "Make" variable.
+ */
+ public interface Context {
+
+ /**
+ * Returns the expansion of the specified "Make" variable.
+ *
+ * @param var the variable to expand.
+ * @return the expansion of the variable.
+ * @throws ExpansionException if the variable "var" was not defined or
+ * there was any other error while expanding "var".
+ */
+ String lookupMakeVariable(String var) throws ExpansionException;
+ }
+
+ /**
+ * Exception thrown by MakeVariableExpander.Context.expandVariable when an
+ * unknown variable is passed.
+ */
+ public static class ExpansionException extends Exception {
+ public ExpansionException(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * Expands all references to "Make" variables embedded within string "expr",
+ * using the provided Context instance to expand individual variables.
+ *
+ * @param expression the string to expand.
+ * @param context the context which defines the expansion of each individual
+ * variable.
+ * @return the expansion of "expr".
+ * @throws ExpansionException if "expr" contained undefined or ill-formed
+ * variables references.
+ */
+ public static String expand(String expression, Context context) throws ExpansionException {
+ if (expression.indexOf('$') < 0) {
+ return expression;
+ }
+ return expand(expression, context, 0);
+ }
+
+ /**
+ * If the string contains a single variable, return the expansion of that variable.
+ * Otherwise, return null.
+ */
+ public static String expandSingleVariable(String expression, Context context)
+ throws ExpansionException {
+ String var = new MakeVariableExpander(expression).getSingleVariable();
+ return (var != null) ? context.lookupMakeVariable(var) : null;
+ }
+
+ // Helper method for counting recursion depth.
+ private static String expand(String expression, Context context, int depth)
+ throws ExpansionException {
+ if (depth > 10) { // plenty!
+ throw new ExpansionException("potentially unbounded recursion during "
+ + "expansion of '" + expression + "'");
+ }
+ return new MakeVariableExpander(expression).expand(context, depth);
+ }
+
+ private String expand(Context context, int depth) throws ExpansionException {
+ StringBuilder result = new StringBuilder();
+ while (offset < length) {
+ char c = buffer[offset];
+ if (c == '$') { // variable
+ offset++;
+ if (offset >= length) {
+ throw new ExpansionException("unterminated $");
+ }
+ if (buffer[offset] == '$') {
+ result.append('$');
+ } else {
+ String var = scanVariable();
+ String value = context.lookupMakeVariable(var);
+ // To prevent infinite recursion for the ignored shell variables
+ if (!value.equals(var)) {
+ // recursively expand using Make's ":=" semantics:
+ value = expand(value, context, depth + 1);
+ }
+ result.append(value);
+ }
+ } else {
+ result.append(c);
+ }
+ offset++;
+ }
+ return result.toString();
+ }
+
+ /**
+ * Starting at the current position, scans forward until the name of a Make
+ * variable has been consumed. Returns the variable name and advances the
+ * position. If the variable is a potential shell variable returns the shell
+ * variable expression itself, so that we can let the shell handle the
+ * expansion.
+ *
+ * @return the name of the variable found at the current point.
+ * @throws ExpansionException if the variable reference was ill-formed.
+ */
+ private String scanVariable() throws ExpansionException {
+ char c = buffer[offset];
+ switch (c) {
+ case '(': { // $(SRCS)
+ offset++;
+ int start = offset;
+ while (offset < length && buffer[offset] != ')') {
+ offset++;
+ }
+ if (offset >= length) {
+ throw new ExpansionException("unterminated variable reference");
+ }
+ return new String(buffer, start, offset - start);
+ }
+ case '{': { // ${SRCS}
+ offset++;
+ int start = offset;
+ while (offset < length && buffer[offset] != '}') {
+ offset++;
+ }
+ if (offset >= length) {
+ throw new ExpansionException("unterminated variable reference");
+ }
+ String expr = new String(buffer, start, offset - start);
+ throw new ExpansionException("'${" + expr + "}' syntax is not supported; use '$(" + expr
+ + ")' instead for \"Make\" variables, or escape the '$' as "
+ + "'$$' if you intended this for the shell");
+ }
+ case '@':
+ case '<':
+ case '^':
+ return String.valueOf(c);
+ default: {
+ int start = offset;
+ while (offset + 1 < length && Character.isJavaIdentifierPart(buffer[offset + 1])) {
+ offset++;
+ }
+ String expr = new String(buffer, start, offset + 1 - start);
+ throw new ExpansionException("'$" + expr + "' syntax is not supported; use '$(" + expr
+ + ")' instead for \"Make\" variables, or escape the '$' as "
+ + "'$$' if you intended this for the shell");
+ }
+ }
+ }
+
+ /**
+ * @return the variable name if the variable spans from offset to the end of
+ * the buffer, otherwise return null.
+ * @throws ExpansionException if the variable reference was ill-formed.
+ */
+ public String getSingleVariable() throws ExpansionException {
+ if (buffer[offset] == '$') {
+ offset++;
+ String result = scanVariable();
+ if (offset + 1 == length) {
+ return result;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/MiddlemanProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/MiddlemanProvider.java
new file mode 100644
index 0000000..d8425f2
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/MiddlemanProvider.java
@@ -0,0 +1,38 @@
+// 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+/**
+ * A provider class that supplies an aggregating middleman to the targets that depend on it.
+ */
+@Immutable
+public final class MiddlemanProvider implements TransitiveInfoProvider {
+
+ private final NestedSet<Artifact> middlemanArtifact;
+
+ public MiddlemanProvider(NestedSet<Artifact> middlemanArtifact) {
+ this.middlemanArtifact = middlemanArtifact;
+ }
+
+ /**
+ * Returns the middleman for the files produced by the transitive info collection.
+ */
+ public NestedSet<Artifact> getMiddlemanArtifact() {
+ return middlemanArtifact;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/NoSuchConfiguredTargetException.java b/src/main/java/com/google/devtools/build/lib/analysis/NoSuchConfiguredTargetException.java
new file mode 100644
index 0000000..2e9bf8c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/NoSuchConfiguredTargetException.java
@@ -0,0 +1,29 @@
+// 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.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.packages.NoSuchThingException;
+import com.google.devtools.build.lib.syntax.Label;
+
+/**
+ * Exception indicating that the required configured target is not in the
+ * analysis cache.
+ */
+public class NoSuchConfiguredTargetException extends NoSuchThingException {
+ public NoSuchConfiguredTargetException(Label label, BuildConfiguration configuration) {
+ super("not in cache: " + label + " " + configuration);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/OutputFileConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/OutputFileConfiguredTarget.java
new file mode 100644
index 0000000..51122e2
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/OutputFileConfiguredTarget.java
@@ -0,0 +1,80 @@
+// 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.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.packages.OutputFile;
+import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider;
+import com.google.devtools.build.lib.rules.test.InstrumentedFilesProviderImpl;
+
+/**
+ * A ConfiguredTarget for an OutputFile.
+ */
+public class OutputFileConfiguredTarget extends FileConfiguredTarget
+ implements InstrumentedFilesProvider {
+
+ private final TransitiveInfoCollection generatingRule;
+
+ OutputFileConfiguredTarget(
+ TargetContext targetContext, OutputFile outputFile,
+ TransitiveInfoCollection generatingRule, Artifact outputArtifact) {
+ super(targetContext, outputArtifact);
+ Preconditions.checkArgument(targetContext.getTarget() == outputFile);
+ this.generatingRule = generatingRule;
+ }
+
+ @Override
+ public OutputFile getTarget() {
+ return (OutputFile) super.getTarget();
+ }
+
+ public TransitiveInfoCollection getGeneratingRule() {
+ return generatingRule;
+ }
+
+ @Override
+ public NestedSet<TargetLicense> getTransitiveLicenses() {
+ return getProvider(LicensesProvider.class, LicensesProviderImpl.EMPTY)
+ .getTransitiveLicenses();
+ }
+
+ @Override
+ public NestedSet<Artifact> getInstrumentedFiles() {
+ return getProvider(InstrumentedFilesProvider.class, InstrumentedFilesProviderImpl.EMPTY)
+ .getInstrumentedFiles();
+ }
+
+ @Override
+ public NestedSet<Artifact> getInstrumentationMetadataFiles() {
+ return getProvider(InstrumentedFilesProvider.class, InstrumentedFilesProviderImpl.EMPTY)
+ .getInstrumentationMetadataFiles();
+ }
+
+ /**
+ * Returns the corresponding provider from the generating rule, if it is non-null, or {@code
+ * defaultValue} otherwise.
+ */
+ private <T extends TransitiveInfoProvider> T getProvider(Class<T> clazz, T defaultValue) {
+ if (generatingRule != null) {
+ T result = generatingRule.getProvider(clazz);
+ if (result != null) {
+ return result;
+ }
+ }
+ return defaultValue;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PackageGroupConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/PackageGroupConfiguredTarget.java
new file mode 100644
index 0000000..75e2981
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/PackageGroupConfiguredTarget.java
@@ -0,0 +1,77 @@
+// 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.UnmodifiableIterator;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.packages.PackageGroup;
+import com.google.devtools.build.lib.packages.PackageSpecification;
+import com.google.devtools.build.lib.syntax.Label;
+
+/**
+ * Dummy ConfiguredTarget for package groups. Contains no functionality, since
+ * package groups are not really first-class Targets.
+ */
+public final class PackageGroupConfiguredTarget extends AbstractConfiguredTarget
+ implements PackageSpecificationProvider {
+ private final NestedSet<PackageSpecification> packageSpecifications;
+
+ PackageGroupConfiguredTarget(TargetContext targetContext, PackageGroup packageGroup) {
+ super(targetContext);
+ Preconditions.checkArgument(targetContext.getConfiguration() == null);
+
+ NestedSetBuilder<PackageSpecification> builder =
+ NestedSetBuilder.stableOrder();
+ for (Label label : packageGroup.getIncludes()) {
+ TransitiveInfoCollection include = targetContext.findDirectPrerequisite(
+ label, targetContext.getConfiguration());
+ PackageSpecificationProvider provider = include == null ? null :
+ include.getProvider(PackageSpecificationProvider.class);
+ if (provider == null) {
+ targetContext.getAnalysisEnvironment().getEventHandler().handle(Event.error(getTarget().getLocation(),
+ String.format("label '%s' does not refer to a package group", label)));
+ continue;
+ }
+
+ builder.addTransitive(provider.getPackageSpecifications());
+ }
+
+ builder.addAll(packageGroup.getPackageSpecifications());
+ packageSpecifications = builder.build();
+ }
+
+ @Override
+ public PackageGroup getTarget() {
+ return (PackageGroup) super.getTarget();
+ }
+
+ @Override
+ public NestedSet<PackageSpecification> getPackageSpecifications() {
+ return packageSpecifications;
+ }
+
+ @Override
+ public Object get(String providerKey) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public UnmodifiableIterator<TransitiveInfoProvider> iterator() {
+ throw new IllegalStateException();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PackageSpecificationProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/PackageSpecificationProvider.java
new file mode 100644
index 0000000..3f852c7
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/PackageSpecificationProvider.java
@@ -0,0 +1,26 @@
+// 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.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.packages.PackageSpecification;
+
+/**
+ * A {@link TransitiveInfoProvider} that describes a set of transitive package specifications
+ * used in package groups.
+ */
+public interface PackageSpecificationProvider extends TransitiveInfoProvider {
+ NestedSet<PackageSpecification> getPackageSpecifications();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PrerequisiteArtifacts.java b/src/main/java/com/google/devtools/build/lib/analysis/PrerequisiteArtifacts.java
new file mode 100644
index 0000000..8932d5c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/PrerequisiteArtifacts.java
@@ -0,0 +1,106 @@
+// 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.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.util.FileType;
+import com.google.devtools.build.lib.util.FileTypeSet;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Contains a sequence of prerequisite artifacts and supplies methods for filtering and reporting
+ * errors on those artifacts.
+ */
+public final class PrerequisiteArtifacts {
+ private final RuleContext ruleContext;
+ private final String attributeName;
+ private final ImmutableList<Artifact> artifacts;
+
+ private PrerequisiteArtifacts(
+ RuleContext ruleContext, String attributeName, ImmutableList<Artifact> artifacts) {
+ this.ruleContext = Preconditions.checkNotNull(ruleContext);
+ this.attributeName = Preconditions.checkNotNull(attributeName);
+ this.artifacts = Preconditions.checkNotNull(artifacts);
+ }
+
+ static PrerequisiteArtifacts get(RuleContext ruleContext, String attributeName, Mode mode) {
+ Set<Artifact> result = new LinkedHashSet<>();
+ for (FileProvider target :
+ ruleContext.getPrerequisites(attributeName, mode, FileProvider.class)) {
+ Iterables.addAll(result, target.getFilesToBuild());
+ }
+ return new PrerequisiteArtifacts(ruleContext, attributeName, ImmutableList.copyOf(result));
+ }
+
+ /**
+ * Returns the artifacts this instance contains in an {@link ImmutableList}.
+ */
+ public ImmutableList<Artifact> list() {
+ return artifacts;
+ }
+
+ private PrerequisiteArtifacts filter(Predicate<String> fileType, boolean errorsForNonMatching) {
+ ImmutableList.Builder<Artifact> filtered = new ImmutableList.Builder<Artifact>();
+
+ for (Artifact artifact : artifacts) {
+ if (fileType.apply(artifact.getFilename())) {
+ filtered.add(artifact);
+ } else if (errorsForNonMatching) {
+ ruleContext.attributeError(
+ attributeName,
+ String.format("%s does not match expected type: %s", artifact, fileType));
+ }
+ }
+
+ return new PrerequisiteArtifacts(ruleContext, attributeName, filtered.build());
+ }
+
+ /**
+ * Returns an equivalent instance but only containing artifacts of the given type, reporting
+ * errors for non-matching artifacts.
+ */
+ public PrerequisiteArtifacts errorsForNonMatching(FileType fileType) {
+ return filter(fileType, /*errorsForNonMatching=*/true);
+ }
+
+ /**
+ * Returns an equivalent instance but only containing artifacts of the given types, reporting
+ * errors for non-matching artifacts.
+ */
+ public PrerequisiteArtifacts errorsForNonMatching(FileTypeSet fileTypeSet) {
+ return filter(fileTypeSet, /*errorsForNonMatching=*/true);
+ }
+
+ /**
+ * Returns an equivalent instance but only containing artifacts of the given type.
+ */
+ public PrerequisiteArtifacts filter(FileType fileType) {
+ return filter(fileType, /*errorsForNonMatching=*/false);
+ }
+
+ /**
+ * Returns an equivalent instance but only containing artifacts of the given types.
+ */
+ public PrerequisiteArtifacts filter(FileTypeSet fileTypeSet) {
+ return filter(fileTypeSet, /*errorsForNonMatching=*/false);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PrintActionVisitor.java b/src/main/java/com/google/devtools/build/lib/analysis/PrintActionVisitor.java
new file mode 100644
index 0000000..c852734
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/PrintActionVisitor.java
@@ -0,0 +1,66 @@
+// 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.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionGraph;
+import com.google.devtools.build.lib.actions.ActionGraphVisitor;
+import com.google.devtools.build.lib.actions.ActionOwner;
+
+import java.util.List;
+
+/**
+ * A bipartite graph visitor which accumulates actions with matching mnemonics for a target.
+ */
+public final class PrintActionVisitor extends ActionGraphVisitor {
+ private final ConfiguredTarget target;
+ private final List<Action> actions;
+ private final Predicate<Action> actionMnemonicMatcher;
+ private final String targetConfigurationKey;
+
+ /**
+ * Creates a new visitor for the actions associated with the given target that have a matching
+ * mnemonic.
+ */
+ public PrintActionVisitor(ActionGraph actionGraph, ConfiguredTarget target,
+ Predicate<Action> actionMnemonicMatcher) {
+ super(actionGraph);
+ this.target = target;
+ this.actionMnemonicMatcher = actionMnemonicMatcher;
+ actions = Lists.newArrayList();
+ targetConfigurationKey = target.getConfiguration().shortCacheKey();
+ }
+
+ @Override
+ protected boolean shouldVisit(Action action) {
+ ActionOwner owner = action.getOwner();
+ return owner != null && target.getLabel().equals(owner.getLabel())
+ && targetConfigurationKey.equals(owner.getConfigurationShortCacheKey());
+ }
+
+ @Override
+ protected void visitAction(Action action) {
+ if (actionMnemonicMatcher.apply(action)) {
+ actions.add(action);
+ }
+ }
+
+ /** Retrieves the collected actions since this method was last called and clears the list. */
+ public ImmutableList<Action> getActions() {
+ return ImmutableList.copyOf(actions);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PseudoAction.java b/src/main/java/com/google/devtools/build/lib/analysis/PseudoAction.java
new file mode 100644
index 0000000..00d43a3
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/PseudoAction.java
@@ -0,0 +1,95 @@
+// 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.devtools.build.lib.actions.AbstractAction;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.actions.extra.ExtraActionInfo;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.protobuf.GeneratedMessage.GeneratedExtension;
+import com.google.protobuf.MessageLite;
+
+import java.util.Collection;
+import java.util.UUID;
+
+/**
+ * An action that is inserted into the build graph only to provide info
+ * about rules to extra_actions.
+ */
+public class PseudoAction<InfoType extends MessageLite> extends AbstractAction {
+
+ private final UUID uuid;
+ private final String mnemonic;
+ private final GeneratedExtension<ExtraActionInfo, InfoType> infoExtension;
+ private final InfoType info;
+
+ public PseudoAction(UUID uuid, ActionOwner owner,
+ Collection<Artifact> inputs, Collection<Artifact> outputs,
+ String mnemonic,
+ GeneratedExtension<ExtraActionInfo, InfoType> infoExtension, InfoType info) {
+ super(owner, inputs, outputs);
+ this.uuid = uuid;
+ this.mnemonic = mnemonic;
+ this.infoExtension = infoExtension;
+ this.info = info;
+ }
+
+ @Override
+ public String describeStrategy(Executor executor) {
+ return null;
+ }
+
+ @Override
+ public void execute(ActionExecutionContext actionExecutionContext)
+ throws ActionExecutionException {
+ throw new ActionExecutionException(
+ mnemonic + "ExtraAction should not be executed.", this, false);
+ }
+
+ @Override
+ public String getMnemonic() {
+ return mnemonic;
+ }
+
+ @Override
+ protected String computeKey() {
+ return new Fingerprint()
+ .addUUID(uuid)
+ .addBytes(info.toByteArray())
+ .hexDigestAndReset();
+ }
+
+ @Override
+ public ResourceSet estimateResourceConsumption(Executor executor) {
+ return ResourceSet.ZERO;
+ }
+
+ @Override
+ public ExtraActionInfo.Builder getExtraActionInfo() {
+ return super.getExtraActionInfo().setExtension(infoExtension, info);
+ }
+
+ public static Artifact getDummyOutput(RuleContext ruleContext) {
+ return ruleContext.getAnalysisEnvironment().getDerivedArtifact(
+ ruleContext.getLabel().toPathFragment().replaceName(
+ ruleContext.getLabel().getName() + ".extra_action_dummy"),
+ ruleContext.getConfiguration().getGenfilesDirectory());
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RedirectChaser.java b/src/main/java/com/google/devtools/build/lib/analysis/RedirectChaser.java
new file mode 100644
index 0000000..108a577
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RedirectChaser.java
@@ -0,0 +1,114 @@
+// 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.devtools.build.lib.analysis.config.ConfigurationEnvironment;
+import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
+import com.google.devtools.build.lib.packages.AbstractAttributeMapper;
+import com.google.devtools.build.lib.packages.NoSuchPackageException;
+import com.google.devtools.build.lib.packages.NoSuchTargetException;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.syntax.Label;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * Tool for chasing filegroup redirects. This is mainly intended to be used during
+ * BuildConfiguration creation.
+ */
+public final class RedirectChaser {
+
+ /**
+ * Custom attribute mapper that throws an exception if an attribute's value depends on the
+ * build configuration.
+ */
+ private static class StaticValuedAttributeMapper extends AbstractAttributeMapper {
+ public StaticValuedAttributeMapper(Rule rule) {
+ super(rule.getPackage(), rule.getRuleClassObject(), rule.getLabel(),
+ rule.getAttributeContainer());
+ }
+
+ /**
+ * Returns the value of the given attribute.
+ *
+ * @throws InvalidConfigurationException if the value is configuration-dependent
+ */
+ public <T> T getAndValidate(String attributeName, Type<T> type)
+ throws InvalidConfigurationException {
+ if (getSelector(attributeName, type) != null) {
+ throw new InvalidConfigurationException
+ ("The value of '" + attributeName + "' cannot be configuration-dependent");
+ }
+ return super.get(attributeName, type);
+ }
+
+ @Override
+ protected <T> Iterable<T> visitAttribute(String attributeName, Type<T> type) {
+ throw new IllegalStateException("Attribute visitation not supported redirect resolution");
+ }
+ }
+
+ /**
+ * Follows the 'srcs' attribute of the given label recursively. Keeps repeating as long as the
+ * labels are filegroups with a single srcs entry.
+ *
+ * @param env for loading the packages
+ * @param label the label to start at
+ * @param name user-meaningful description of the content being resolved
+ * @return the label which cannot be further resolved
+ * @throws InvalidConfigurationException if something goes wrong
+ */
+ @Nullable
+ public static Label followRedirects(ConfigurationEnvironment env, Label label, String name)
+ throws InvalidConfigurationException {
+ Set<Label> visitedLabels = new HashSet<>();
+ visitedLabels.add(label);
+ try {
+ while (true) {
+ Target possibleRedirect = env.getTarget(label);
+ if (possibleRedirect == null) {
+ return null;
+ }
+ if ((possibleRedirect instanceof Rule) &&
+ "filegroup".equals(((Rule) possibleRedirect).getRuleClass())) {
+ List<Label> labels = new StaticValuedAttributeMapper((Rule) possibleRedirect)
+ .getAndValidate("srcs", Type.LABEL_LIST);
+ if (labels.size() != 1) {
+ // We can't distinguish redirects from the final filegroup, so we assume this must be
+ // the final one.
+ return label;
+ }
+ label = labels.get(0);
+ if (!visitedLabels.add(label)) {
+ throw new InvalidConfigurationException("The " + name + " points to a filegroup which "
+ + "recursively includes itself. The label " + label + " is part of the loop");
+ }
+ } else {
+ return label;
+ }
+ }
+ } catch (NoSuchPackageException e) {
+ throw new InvalidConfigurationException(e.getMessage(), e);
+ } catch (NoSuchTargetException e) {
+ return label;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTarget.java
new file mode 100644
index 0000000..602e949
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTarget.java
@@ -0,0 +1,226 @@
+// 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.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.UnmodifiableIterator;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
+import com.google.devtools.build.lib.analysis.config.RunUnder;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.packages.OutputFile;
+import com.google.devtools.build.lib.packages.Rule;
+
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A generic implementation of RuleConfiguredTarget. Do not use directly. Use {@link
+ * RuleConfiguredTargetBuilder} instead.
+ */
+public final class RuleConfiguredTarget extends AbstractConfiguredTarget {
+ /**
+ * The configuration transition for an attribute through which a prerequisite
+ * is requested.
+ */
+ public enum Mode {
+ TARGET,
+ HOST,
+ DATA,
+ SPLIT,
+ DONT_CHECK
+ }
+
+ private final ImmutableMap<Class<? extends TransitiveInfoProvider>, Object> providers;
+ private final ImmutableList<Artifact> mandatoryStampFiles;
+ private final Set<ConfigMatchingProvider> configConditions;
+ private final ImmutableList<Aspect> aspects;
+
+ RuleConfiguredTarget(RuleContext ruleContext,
+ ImmutableList<Artifact> mandatoryStampFiles,
+ ImmutableMap<String, Object> skylarkProviders,
+ Map<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> providers) {
+ super(ruleContext);
+ // We don't use ImmutableMap.Builder here to allow augmenting the initial list of 'default'
+ // providers by passing them in.
+ Map<Class<? extends TransitiveInfoProvider>, Object> providerBuilder = new LinkedHashMap<>();
+ providerBuilder.putAll(providers);
+ Preconditions.checkState(providerBuilder.containsKey(RunfilesProvider.class));
+ Preconditions.checkState(providerBuilder.containsKey(FileProvider.class));
+ Preconditions.checkState(providerBuilder.containsKey(FilesToRunProvider.class));
+
+ providerBuilder.put(SkylarkProviders.class, new SkylarkProviders(skylarkProviders));
+
+ this.providers = ImmutableMap.copyOf(providerBuilder);
+ this.mandatoryStampFiles = mandatoryStampFiles;
+ this.configConditions = ruleContext.getConfigConditions();
+ this.aspects = ImmutableList.of();
+
+ // If this rule is the run_under target, then check that we have an executable; note that
+ // run_under is only set in the target configuration, and the target must also be analyzed for
+ // the target configuration.
+ RunUnder runUnder = getConfiguration().getRunUnder();
+ if (runUnder != null && getLabel().equals(runUnder.getLabel())) {
+ if (getProvider(FilesToRunProvider.class).getExecutable() == null) {
+ ruleContext.ruleError("run_under target " + runUnder.getLabel() + " is not executable");
+ }
+ }
+
+ // Make sure that all declared output files are also created as artifacts. The
+ // CachingAnalysisEnvironment makes sure that they all have generating actions.
+ if (!ruleContext.hasErrors()) {
+ for (OutputFile out : ruleContext.getRule().getOutputFiles()) {
+ ruleContext.createOutputArtifact(out);
+ }
+ }
+ }
+
+ /**
+ * Merge a configured target with its associated aspects.
+ *
+ * <p>If aspects are present, the configured target must be created from a rule (instead of e.g.
+ * an input or an output file).
+ */
+ public static ConfiguredTarget mergeAspects(
+ ConfiguredTarget base, Iterable<Aspect> aspects) {
+ if (Iterables.isEmpty(aspects)) {
+ // If there are no aspects, don't bother with creating a proxy object
+ return base;
+ } else {
+ // Aspects can only be attached to rules for now. This invariant is upheld by
+ // DependencyResolver#requiredAspects()
+ return new RuleConfiguredTarget((RuleConfiguredTarget) base, aspects);
+ }
+ }
+
+ /**
+ * Creates an instance based on a configured target and a set of aspects.
+ */
+ private RuleConfiguredTarget(RuleConfiguredTarget base, Iterable<Aspect> aspects) {
+ super(base.getTarget(), base.getConfiguration());
+
+ Set<Class<? extends TransitiveInfoProvider>> providers = new HashSet<>();
+
+ providers.addAll(base.providers.keySet());
+ for (Aspect aspect : aspects) {
+ for (TransitiveInfoProvider aspectProvider : aspect) {
+ if (!providers.add(aspectProvider.getClass())) {
+ throw new IllegalStateException(
+ "Provider " + aspectProvider.getClass() + " provided twice");
+ }
+ }
+ }
+ this.providers = base.providers;
+ this.mandatoryStampFiles = base.mandatoryStampFiles;
+ this.configConditions = base.configConditions;
+ this.aspects = ImmutableList.copyOf(aspects);
+ }
+
+ /**
+ * The configuration conditions that trigger this rule's configurable attributes.
+ */
+ Set<ConfigMatchingProvider> getConfigConditions() {
+ return configConditions;
+ }
+
+ @Override
+ public <P extends TransitiveInfoProvider> P getProvider(Class<P> providerClass) {
+ AnalysisUtils.checkProvider(providerClass);
+ // TODO(bazel-team): Should aspects be allowed to override providers on the configured target
+ // class?
+ Object provider = providers.get(providerClass);
+ if (provider == null) {
+ for (Aspect aspect : aspects) {
+ provider = aspect.getProviders().get(providerClass);
+ if (provider != null) {
+ break;
+ }
+ }
+ }
+
+ return providerClass.cast(provider);
+ }
+
+ /**
+ * Returns a value provided by this target. Only meant to use from Skylark.
+ */
+ @Override
+ public Object get(String providerKey) {
+ return getProvider(SkylarkProviders.class).skylarkProviders.get(providerKey);
+ }
+
+ public ImmutableList<Artifact> getMandatoryStampFiles() {
+ return mandatoryStampFiles;
+ }
+
+ @Override
+ public final Rule getTarget() {
+ return (Rule) super.getTarget();
+ }
+
+ /**
+ * A helper class for transitive infos provided by Skylark rule implementations.
+ */
+ @Immutable
+ public static final class SkylarkProviders implements TransitiveInfoProvider {
+ private final ImmutableMap<String, Object> skylarkProviders;
+
+ private SkylarkProviders(ImmutableMap<String, Object> skylarkProviders) {
+ Preconditions.checkNotNull(skylarkProviders);
+ this.skylarkProviders = skylarkProviders;
+ }
+
+ /**
+ * Returns the keys for the Skylark providers.
+ */
+ public ImmutableCollection<String> getKeys() {
+ return skylarkProviders.keySet();
+ }
+ }
+
+ @Override
+ public UnmodifiableIterator<TransitiveInfoProvider> iterator() {
+ Map<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> allProviders =
+ new LinkedHashMap<>();
+ for (int i = aspects.size() - 1; i >= 0; i++) {
+ for (TransitiveInfoProvider tip : aspects.get(i)) {
+ allProviders.put(tip.getClass(), tip);
+ }
+ }
+
+ for (Map.Entry<Class<? extends TransitiveInfoProvider>, Object> entry : providers.entrySet()) {
+ allProviders.put(entry.getKey(), entry.getKey().cast(entry.getValue()));
+ }
+
+ return ImmutableList.copyOf(allProviders.values()).iterator();
+ }
+
+ @Override
+ public String errorMessage(String name) {
+ return String.format("target (rule class of '%s') doesn't have provider '%s'.",
+ getTarget().getRuleClass(), name);
+ }
+
+ @Override
+ public ImmutableCollection<String> getKeys() {
+ return ImmutableList.<String>builder().addAll(super.getKeys())
+ .addAll(getProvider(SkylarkProviders.class).skylarkProviders.keySet()).build();
+ }
+}
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 0000000..b82713f
--- /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;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
new file mode 100644
index 0000000..9ad7c70
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
@@ -0,0 +1,1391 @@
+// 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.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.Sets;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.ActionRegistry;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ArtifactOwner;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider.PrerequisiteValidator;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment;
+import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
+import com.google.devtools.build.lib.collect.ImmutableSortedKeyListMultimap;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition;
+import com.google.devtools.build.lib.packages.Attribute.SplitTransition;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.FileTarget;
+import com.google.devtools.build.lib.packages.ImplicitOutputsFunction;
+import com.google.devtools.build.lib.packages.InputFile;
+import com.google.devtools.build.lib.packages.OutputFile;
+import com.google.devtools.build.lib.packages.PackageSpecification;
+import com.google.devtools.build.lib.packages.RawAttributeMapper;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleErrorConsumer;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.packages.TargetUtils;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.rules.fileset.FilesetProvider;
+import com.google.devtools.build.lib.shell.ShellUtils;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.FilesetEntry;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.util.FileTypeSet;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * A helper class for rule implementations building and initialization. Objects of this
+ * class are intended to be passed to the builder for the configured target, which then creates the
+ * configured target.
+ */
+public final class RuleContext extends TargetContext
+ implements ActionConstructionContext, ActionRegistry, RuleErrorConsumer {
+
+ /**
+ * The configured version of FilesetEntry.
+ */
+ @Immutable
+ public static final class ConfiguredFilesetEntry {
+ private final FilesetEntry entry;
+ private final TransitiveInfoCollection src;
+ private final ImmutableList<TransitiveInfoCollection> files;
+
+ ConfiguredFilesetEntry(FilesetEntry entry, TransitiveInfoCollection src) {
+ this.entry = entry;
+ this.src = src;
+ this.files = null;
+ }
+
+ ConfiguredFilesetEntry(FilesetEntry entry, ImmutableList<TransitiveInfoCollection> files) {
+ this.entry = entry;
+ this.src = null;
+ this.files = files;
+ }
+
+ public FilesetEntry getEntry() {
+ return entry;
+ }
+
+ public TransitiveInfoCollection getSrc() {
+ return src;
+ }
+
+ /**
+ * Targets from FilesetEntry.files, or null if the user omitted it.
+ */
+ @Nullable
+ public List<TransitiveInfoCollection> getFiles() {
+ return files;
+ }
+ }
+
+ static final String HOST_CONFIGURATION_PROGRESS_TAG = "for host";
+
+ private final Rule rule;
+ private final ListMultimap<String, ConfiguredTarget> targetMap;
+ private final ListMultimap<String, ConfiguredFilesetEntry> filesetEntryMap;
+ private final Set<ConfigMatchingProvider> configConditions;
+ private final AttributeMap attributes;
+ private final ImmutableSet<String> features;
+
+ private ActionOwner actionOwner;
+
+ /* lazily computed cache for Make variables, computed from the above. See get... method */
+ private transient ConfigurationMakeVariableContext configurationMakeVariableContext = null;
+
+ private RuleContext(Builder builder, ListMultimap<String, ConfiguredTarget> targetMap,
+ ListMultimap<String, ConfiguredFilesetEntry> filesetEntryMap,
+ Set<ConfigMatchingProvider> configConditions, ImmutableSet<String> features) {
+ super(builder.env, builder.rule, builder.configuration, builder.prerequisiteMap.get(null),
+ builder.visibility);
+ this.rule = builder.rule;
+ this.targetMap = targetMap;
+ this.filesetEntryMap = filesetEntryMap;
+ this.configConditions = configConditions;
+ this.attributes =
+ ConfiguredAttributeMapper.of(builder.rule, configConditions);
+ this.features = features;
+ }
+
+ @Override
+ public Rule getRule() {
+ return rule;
+ }
+
+ /**
+ * The configuration conditions that trigger this rule's configurable attributes.
+ */
+ Set<ConfigMatchingProvider> getConfigConditions() {
+ return configConditions;
+ }
+
+ /**
+ * Returns the host configuration for this rule; keep in mind that there may be multiple different
+ * host configurations, even during a single build.
+ */
+ public BuildConfiguration getHostConfiguration() {
+ BuildConfiguration configuration = getConfiguration();
+ // Note: the Builder checks that the configuration is non-null.
+ return configuration.getConfiguration(ConfigurationTransition.HOST);
+ }
+
+ /**
+ * Accessor for the Rule's attribute values.
+ */
+ public AttributeMap attributes() {
+ return attributes;
+ }
+
+ /**
+ * Returns whether this instance is known to have errors at this point during analysis. Do not
+ * call this method after the initializationHook has returned.
+ */
+ public boolean hasErrors() {
+ return getAnalysisEnvironment().hasErrors();
+ }
+
+ /**
+ * Returns an immutable map from attribute name to list of configured targets for that attribute.
+ */
+ public ListMultimap<String, ? extends TransitiveInfoCollection> getConfiguredTargetMap() {
+ return targetMap;
+ }
+
+ /**
+ * Returns an immutable map from attribute name to list of fileset entries.
+ */
+ public ListMultimap<String, ConfiguredFilesetEntry> getFilesetEntryMap() {
+ return filesetEntryMap;
+ }
+
+ @Override
+ public ActionOwner getActionOwner() {
+ if (actionOwner == null) {
+ actionOwner = new RuleActionOwner(rule, getConfiguration());
+ }
+ return actionOwner;
+ }
+
+ /**
+ * Returns a configuration fragment for this this target.
+ */
+ @Nullable
+ public <T extends Fragment> T getFragment(Class<T> fragment) {
+ // TODO(bazel-team): The fragments can also be accessed directly through BuildConfiguration.
+ // Can we lock that down somehow?
+ Preconditions.checkArgument(
+ rule.getRuleClassObject().isLegalConfigurationFragment(fragment),
+ "%s does not have access to %s", rule.getRuleClass(), fragment);
+ return getConfiguration().getFragment(fragment);
+ }
+
+ @Override
+ public ArtifactOwner getOwner() {
+ return getAnalysisEnvironment().getOwner();
+ }
+
+ // TODO(bazel-team): This class could be simpler if Rule and BuildConfiguration classes
+ // were immutable. Then we would need to store only references those two.
+ @Immutable
+ private static final class RuleActionOwner implements ActionOwner {
+ private final Label label;
+ private final Location location;
+ private final String configurationName;
+ private final String mnemonic;
+ private final String targetKind;
+ private final String shortCacheKey;
+ private final boolean hostConfiguration;
+
+ private RuleActionOwner(Rule rule, BuildConfiguration configuration) {
+ this.label = rule.getLabel();
+ this.location = rule.getLocation();
+ this.targetKind = rule.getTargetKind();
+ this.configurationName = configuration.getShortName();
+ this.mnemonic = configuration.getMnemonic();
+ this.shortCacheKey = configuration.shortCacheKey();
+ this.hostConfiguration = configuration.isHostConfiguration();
+ }
+
+ @Override
+ public Location getLocation() {
+ return location;
+ }
+
+ @Override
+ public Label getLabel() {
+ return label;
+ }
+
+ @Override
+ public String getConfigurationName() {
+ return configurationName;
+ }
+
+ @Override
+ public String getConfigurationMnemonic() {
+ return mnemonic;
+ }
+
+ @Override
+ public String getConfigurationShortCacheKey() {
+ return shortCacheKey;
+ }
+
+ @Override
+ public String getTargetKind() {
+ return targetKind;
+ }
+
+ @Override
+ public String getAdditionalProgressInfo() {
+ return hostConfiguration ? HOST_CONFIGURATION_PROGRESS_TAG : null;
+ }
+ }
+
+ @Override
+ public void registerAction(Action... action) {
+ getAnalysisEnvironment().registerAction(action);
+ }
+
+ /**
+ * Convenience function for subclasses to report non-attribute-specific
+ * errors in the current rule.
+ */
+ @Override
+ public void ruleError(String message) {
+ reportError(rule.getLocation(), prefixRuleMessage(message));
+ }
+
+ /**
+ * Convenience function for subclasses to report non-attribute-specific
+ * warnings in the current rule.
+ */
+ @Override
+ public void ruleWarning(String message) {
+ reportWarning(rule.getLocation(), prefixRuleMessage(message));
+ }
+
+ /**
+ * Convenience function for subclasses to report attribute-specific errors in
+ * the current rule.
+ *
+ * <p>If the name of the attribute starts with <code>$</code>
+ * it is replaced with a string <code>(an implicit dependency)</code>.
+ */
+ @Override
+ public void attributeError(String attrName, String message) {
+ reportError(rule.getAttributeLocation(attrName),
+ prefixAttributeMessage(Attribute.isImplicit(attrName)
+ ? "(an implicit dependency)"
+ : attrName,
+ message));
+ }
+
+ /**
+ * Like attributeError, but does not mark the configured target as errored.
+ *
+ * <p>If the name of the attribute starts with <code>$</code>
+ * it is replaced with a string <code>(an implicit dependency)</code>.
+ */
+ @Override
+ public void attributeWarning(String attrName, String message) {
+ reportWarning(rule.getAttributeLocation(attrName),
+ prefixAttributeMessage(Attribute.isImplicit(attrName)
+ ? "(an implicit dependency)"
+ : attrName,
+ message));
+ }
+
+ private String prefixAttributeMessage(String attrName, String message) {
+ return "in " + attrName + " attribute of "
+ + rule.getRuleClass() + " rule "
+ + getLabel() + ": " + message;
+ }
+
+ private String prefixRuleMessage(String message) {
+ return "in " + rule.getRuleClass() + " rule "
+ + getLabel() + ": " + message;
+ }
+
+ private void reportError(Location location, String message) {
+ getAnalysisEnvironment().getEventHandler().handle(Event.error(location, message));
+ }
+
+ private void reportWarning(Location location, String message) {
+ getAnalysisEnvironment().getEventHandler().handle(Event.warn(location, message));
+ }
+
+ /**
+ * Returns an artifact beneath the root of either the "bin" or "genfiles"
+ * tree, whose path is based on the name of this target and the current
+ * configuration. The choice of which tree to use is based on the rule with
+ * which this target (which must be an OutputFile or a Rule) is associated.
+ */
+ public Artifact createOutputArtifact() {
+ return internalCreateOutputArtifact(getTarget());
+ }
+
+ /**
+ * Returns the output artifact of an {@link OutputFile} of this target.
+ *
+ * @see #createOutputArtifact()
+ */
+ public Artifact createOutputArtifact(OutputFile out) {
+ return internalCreateOutputArtifact(out);
+ }
+
+ /**
+ * Implementation for {@link #createOutputArtifact()} and
+ * {@link #createOutputArtifact(OutputFile)}. This is private so that
+ * {@link #createOutputArtifact(OutputFile)} can have a more specific
+ * signature.
+ */
+ private Artifact internalCreateOutputArtifact(Target target) {
+ Root root = getBinOrGenfilesDirectory();
+ return getAnalysisEnvironment().getDerivedArtifact(Util.getWorkspaceRelativePath(target), root);
+ }
+
+ /**
+ * Returns the root of either the "bin" or "genfiles"
+ * tree, based on this target and the current configuration.
+ * The choice of which tree to use is based on the rule with
+ * which this target (which must be an OutputFile or a Rule) is associated.
+ */
+ public Root getBinOrGenfilesDirectory() {
+ return rule.hasBinaryOutput()
+ ? getConfiguration().getBinDirectory()
+ : getConfiguration().getGenfilesDirectory();
+ }
+
+ /**
+ * Returns the list of transitive info collections that feed into this target through the
+ * specified attribute. Note that you need to specify the correct mode for the attribute,
+ * otherwise an assertion will be raised.
+ */
+ public List<? extends TransitiveInfoCollection> getPrerequisites(String attributeName,
+ Mode mode) {
+ Attribute attributeDefinition = getRule().getAttributeDefinition(attributeName);
+ if ((mode == Mode.TARGET)
+ && (attributeDefinition.getConfigurationTransition() instanceof SplitTransition)) {
+ // TODO(bazel-team): If you request a split-configured attribute in the target configuration,
+ // we return only the list of configured targets for the first architecture; this is for
+ // backwards compatibility with existing code in cases where the call to getPrerequisites is
+ // deeply nested and we can't easily inject the behavior we want. However, we should fix all
+ // such call sites.
+ checkAttribute(attributeName, Mode.SPLIT);
+ Map<String, ? extends List<? extends TransitiveInfoCollection>> map =
+ getSplitPrerequisites(attributeName, /*requireSplit=*/false);
+ return map.isEmpty()
+ ? ImmutableList.<TransitiveInfoCollection>of()
+ : map.entrySet().iterator().next().getValue();
+ }
+
+ checkAttribute(attributeName, mode);
+ return targetMap.get(attributeName);
+ }
+
+ /**
+ * Returns the a prerequisites keyed by the CPU of their configurations; this method throws an
+ * exception if the split transition is not active.
+ */
+ public Map<String, ? extends List<? extends TransitiveInfoCollection>>
+ getSplitPrerequisites(String attributeName) {
+ return getSplitPrerequisites(attributeName, /*requireSplit*/true);
+ }
+
+ private Map<String, ? extends List<? extends TransitiveInfoCollection>>
+ getSplitPrerequisites(String attributeName, boolean requireSplit) {
+ checkAttribute(attributeName, Mode.SPLIT);
+
+ Attribute attributeDefinition = getRule().getAttributeDefinition(attributeName);
+ SplitTransition<?> transition =
+ (SplitTransition<?>) attributeDefinition.getConfigurationTransition();
+ List<BuildConfiguration> configurations =
+ getConfiguration().getTransitions().getSplitConfigurations(transition);
+ if (configurations.size() == 1) {
+ // There are two cases here:
+ // 1. Splitting is enabled, but only one target cpu.
+ // 2. Splitting is disabled, and no --cpu value was provided on the command line.
+ // In the first case, the cpu value is non-null, but in the second case it is null. We only
+ // allow that to proceed if the caller specified that he is going to ignore the cpu value
+ // anyway.
+ String cpu = configurations.get(0).getCpu();
+ if (cpu == null) {
+ Preconditions.checkState(!requireSplit);
+ cpu = "DO_NOT_USE";
+ }
+ return ImmutableMap.of(cpu, targetMap.get(attributeName));
+ }
+
+ Set<String> cpus = new HashSet<>();
+ for (BuildConfiguration config : configurations) {
+ // This method should only be called when the split config is enabled on the command line, in
+ // which case this cpu can't be null.
+ Preconditions.checkNotNull(config.getCpu());
+ cpus.add(config.getCpu());
+ }
+
+ // Use an ImmutableListMultimap.Builder here to preserve ordering.
+ ImmutableListMultimap.Builder<String, TransitiveInfoCollection> result =
+ ImmutableListMultimap.builder();
+ for (TransitiveInfoCollection t : targetMap.get(attributeName)) {
+ if (t.getConfiguration() != null) {
+ result.put(t.getConfiguration().getCpu(), t);
+ } else {
+ // Source files don't have a configuration, so we add them to all architecture entries.
+ for (String cpu : cpus) {
+ result.put(cpu, t);
+ }
+ }
+ }
+ return Multimaps.asMap(result.build());
+ }
+
+ /**
+ * Returns the specified provider of the prerequisite referenced by the attribute in the
+ * argument. Note that you need to specify the correct mode for the attribute, otherwise an
+ * assertion will be raised. If the attribute is empty of it does not support the specified
+ * provider, returns null.
+ */
+ public <C extends TransitiveInfoProvider> C getPrerequisite(
+ String attributeName, Mode mode, Class<C> provider) {
+ TransitiveInfoCollection prerequisite = getPrerequisite(attributeName, mode);
+ return prerequisite == null ? null : prerequisite.getProvider(provider);
+ }
+
+ /**
+ * Returns the transitive info collection that feeds into this target through the specified
+ * attribute. Note that you need to specify the correct mode for the attribute, otherwise an
+ * assertion will be raised. Returns null if the attribute is empty.
+ */
+ public TransitiveInfoCollection getPrerequisite(String attributeName, Mode mode) {
+ checkAttribute(attributeName, mode);
+ List<? extends TransitiveInfoCollection> elements = targetMap.get(attributeName);
+ if (elements.size() > 1) {
+ throw new IllegalStateException(rule.getRuleClass() + " attribute " + attributeName
+ + " produces more then one prerequisites");
+ }
+ return elements.isEmpty() ? null : elements.get(0);
+ }
+
+ /**
+ * Returns all the providers of the specified type that are listed under the specified attribute
+ * of this target in the BUILD file.
+ */
+ public <C extends TransitiveInfoProvider> Iterable<C> getPrerequisites(String attributeName,
+ Mode mode, final Class<C> classType) {
+ AnalysisUtils.checkProvider(classType);
+ return AnalysisUtils.getProviders(getPrerequisites(attributeName, mode), classType);
+ }
+
+ /**
+ * Returns all the providers of the specified type that are listed under the specified attribute
+ * of this target in the BUILD file, and that contain the specified provider.
+ */
+ public <C extends TransitiveInfoProvider> Iterable<? extends TransitiveInfoCollection>
+ getPrerequisitesIf(String attributeName, Mode mode, final Class<C> classType) {
+ AnalysisUtils.checkProvider(classType);
+ return AnalysisUtils.filterByProvider(getPrerequisites(attributeName, mode), classType);
+ }
+
+ /**
+ * Returns the prerequisite referred to by the specified attribute. Also checks whether
+ * the attribute is marked as executable and that the target referred to can actually be
+ * executed.
+ *
+ * <p>The {@code mode} argument must match the configuration transition specified in the
+ * definition of the attribute.
+ *
+ * @param attributeName the name of the attribute
+ * @param mode the configuration transition of the attribute
+ *
+ * @return the {@link FilesToRunProvider} interface of the prerequisite.
+ */
+ public FilesToRunProvider getExecutablePrerequisite(String attributeName, Mode mode) {
+ Attribute ruleDefinition = getRule().getAttributeDefinition(attributeName);
+
+ if (ruleDefinition == null) {
+ throw new IllegalStateException(getRule().getRuleClass() + " attribute " + attributeName
+ + " is not defined");
+ }
+ if (!ruleDefinition.isExecutable()) {
+ throw new IllegalStateException(getRule().getRuleClass() + " attribute " + attributeName
+ + " is not configured to be executable");
+ }
+
+ TransitiveInfoCollection prerequisite = getPrerequisite(attributeName, mode);
+ if (prerequisite == null) {
+ return null;
+ }
+
+ FilesToRunProvider result = prerequisite.getProvider(FilesToRunProvider.class);
+ if (result == null || result.getExecutable() == null) {
+ attributeError(
+ attributeName, prerequisite.getLabel() + " does not refer to a valid executable target");
+ }
+ return result;
+ }
+
+ /**
+ * Gets an attribute of type STRING_LIST expanding Make variables and
+ * tokenizes the result.
+ *
+ * @param attributeName the name of the attribute to process
+ * @return a list of strings containing the expanded and tokenized values for the
+ * attribute
+ */
+ public List<String> getTokenizedStringListAttr(String attributeName) {
+ if (!getRule().isAttrDefined(attributeName, Type.STRING_LIST)) {
+ // TODO(bazel-team): This should be an error.
+ return ImmutableList.of();
+ }
+ List<String> original = attributes().get(attributeName, Type.STRING_LIST);
+ if (original.isEmpty()) {
+ return ImmutableList.of();
+ }
+ List<String> tokens = new ArrayList<>();
+ for (String token : original) {
+ tokenizeAndExpandMakeVars(tokens, attributeName, token);
+ }
+ return ImmutableList.copyOf(tokens);
+ }
+
+ /**
+ * Expands make variables in value and tokenizes the result into tokens.
+ *
+ * <p>This methods should be called only during initialization.
+ */
+ public void tokenizeAndExpandMakeVars(List<String> tokens, String attributeName,
+ String value) {
+ try {
+ ShellUtils.tokenize(tokens, expandMakeVariables(attributeName, value));
+ } catch (ShellUtils.TokenizationException e) {
+ attributeError(attributeName, e.getMessage());
+ }
+ }
+
+ /**
+ * Return a context that maps Make variable names (string) to values (string).
+ *
+ * @return a ConfigurationMakeVariableContext.
+ **/
+ public ConfigurationMakeVariableContext getConfigurationMakeVariableContext() {
+ if (configurationMakeVariableContext == null) {
+ configurationMakeVariableContext = new ConfigurationMakeVariableContext(
+ getRule().getPackage(), getConfiguration());
+ }
+ return configurationMakeVariableContext;
+ }
+
+ /**
+ * Returns the string "expression" after expanding all embedded references to
+ * "Make" variables. If any errors are encountered, they are reported, and
+ * "expression" is returned unchanged.
+ *
+ * @param attributeName the name of the attribute from which "expression" comes;
+ * used for error reporting.
+ * @param expression the string to expand.
+ * @return the expansion of "expression".
+ */
+ public String expandMakeVariables(String attributeName, String expression) {
+ return expandMakeVariables(attributeName, expression, getConfigurationMakeVariableContext());
+ }
+
+ /**
+ * Returns the string "expression" after expanding all embedded references to
+ * "Make" variables. If any errors are encountered, they are reported, and
+ * "expression" is returned unchanged.
+ *
+ * @param attributeName the name of the attribute from which "expression" comes;
+ * used for error reporting.
+ * @param expression the string to expand.
+ * @param context the ConfigurationMakeVariableContext which can have a customized
+ * lookupMakeVariable(String) method.
+ * @return the expansion of "expression".
+ */
+ public String expandMakeVariables(String attributeName, String expression,
+ ConfigurationMakeVariableContext context) {
+ try {
+ return MakeVariableExpander.expand(expression, context);
+ } catch (MakeVariableExpander.ExpansionException e) {
+ attributeError(attributeName, e.getMessage());
+ return expression;
+ }
+ }
+
+ /**
+ * Gets the value of the STRING_LIST attribute expanding all make variables.
+ */
+ public List<String> expandedMakeVariablesList(String attrName) {
+ List<String> variables = new ArrayList<>();
+ for (String variable : attributes().get(attrName, Type.STRING_LIST)) {
+ variables.add(expandMakeVariables(attrName, variable));
+ }
+ return variables;
+ }
+
+ /**
+ * If the string consists of a single variable, returns the expansion of
+ * that variable. Otherwise, returns null. Syntax errors are reported.
+ *
+ * @param attrName the name of the attribute from which "expression" comes;
+ * used for error reporting.
+ * @param expression the string to expand.
+ * @return the expansion of "expression", or null.
+ */
+ public String expandSingleMakeVariable(String attrName, String expression) {
+ try {
+ return MakeVariableExpander.expandSingleVariable(expression,
+ new ConfigurationMakeVariableContext(getRule().getPackage(), getConfiguration()));
+ } catch (MakeVariableExpander.ExpansionException e) {
+ attributeError(attrName, e.getMessage());
+ return expression;
+ }
+ }
+
+ private void checkAttribute(String attributeName, Mode mode) {
+ Attribute attributeDefinition = getRule().getAttributeDefinition(attributeName);
+ if (attributeDefinition == null) {
+ throw new IllegalStateException(getRule().getLocation() + ": " + getRule().getRuleClass()
+ + " attribute " + attributeName + " is not defined");
+ }
+ if (!(attributeDefinition.getType() == Type.LABEL
+ || attributeDefinition.getType() == Type.LABEL_LIST)) {
+ throw new IllegalStateException(rule.getRuleClass() + " attribute " + attributeName
+ + " is not a label type attribute");
+ }
+ if (mode == Mode.HOST) {
+ if (attributeDefinition.getConfigurationTransition() != ConfigurationTransition.HOST) {
+ throw new IllegalStateException(getRule().getLocation() + ": "
+ + getRule().getRuleClass() + " attribute " + attributeName
+ + " is not configured for the host configuration");
+ }
+ } else if (mode == Mode.TARGET) {
+ if (attributeDefinition.getConfigurationTransition() != ConfigurationTransition.NONE) {
+ throw new IllegalStateException(getRule().getLocation() + ": "
+ + getRule().getRuleClass() + " attribute " + attributeName
+ + " is not configured for the target configuration");
+ }
+ } else if (mode == Mode.DATA) {
+ if (attributeDefinition.getConfigurationTransition() != ConfigurationTransition.DATA) {
+ throw new IllegalStateException(getRule().getLocation() + ": "
+ + getRule().getRuleClass() + " attribute " + attributeName
+ + " is not configured for the data configuration");
+ }
+ } else if (mode == Mode.SPLIT) {
+ if (!(attributeDefinition.getConfigurationTransition() instanceof SplitTransition)) {
+ throw new IllegalStateException(getRule().getLocation() + ": "
+ + getRule().getRuleClass() + " attribute " + attributeName
+ + " is not configured for a split transition");
+ }
+ }
+ }
+
+ /**
+ * Returns the Mode for which the attribute is configured.
+ * This is intended for Skylark, where the Mode is implicitly chosen.
+ */
+ public Mode getAttributeMode(String attributeName) {
+ Attribute attributeDefinition = getRule().getAttributeDefinition(attributeName);
+ if (attributeDefinition == null) {
+ throw new IllegalStateException(getRule().getLocation() + ": " + getRule().getRuleClass()
+ + " attribute " + attributeName + " is not defined");
+ }
+ if (!(attributeDefinition.getType() == Type.LABEL
+ || attributeDefinition.getType() == Type.LABEL_LIST)) {
+ throw new IllegalStateException(rule.getRuleClass() + " attribute " + attributeName
+ + " is not a label type attribute");
+ }
+ if (attributeDefinition.getConfigurationTransition() == ConfigurationTransition.HOST) {
+ return Mode.HOST;
+ } else if (attributeDefinition.getConfigurationTransition() == ConfigurationTransition.NONE) {
+ return Mode.TARGET;
+ } else if (attributeDefinition.getConfigurationTransition() == ConfigurationTransition.DATA) {
+ return Mode.DATA;
+ } else if (attributeDefinition.getConfigurationTransition() instanceof SplitTransition) {
+ return Mode.SPLIT;
+ }
+ throw new IllegalStateException(getRule().getLocation() + ": "
+ + getRule().getRuleClass() + " attribute " + attributeName + " is not configured");
+ }
+
+ /**
+ * For the specified attribute "attributeName" (which must be of type
+ * list(label)), resolve all the labels into ConfiguredTargets (for the
+ * configuration appropriate to the attribute) and return their build
+ * artifacts as a {@link PrerequisiteArtifacts} instance.
+ *
+ * @param attributeName the name of the attribute to traverse
+ */
+ public PrerequisiteArtifacts getPrerequisiteArtifacts(String attributeName, Mode mode) {
+ return PrerequisiteArtifacts.get(this, attributeName, mode);
+ }
+
+ /**
+ * For the specified attribute "attributeName" (which must be of type label),
+ * resolves the ConfiguredTarget and returns its single build artifact.
+ *
+ * <p>If the attribute is optional, has no default and was not specified, then
+ * null will be returned. Note also that null is returned (and an attribute
+ * error is raised) if there wasn't exactly one build artifact for the target.
+ */
+ public Artifact getPrerequisiteArtifact(String attributeName, Mode mode) {
+ TransitiveInfoCollection target = getPrerequisite(attributeName, mode);
+ return transitiveInfoCollectionToArtifact(attributeName, target);
+ }
+
+ /**
+ * Equivalent to getPrerequisiteArtifact(), but also asserts that
+ * host-configuration is appropriate for the specified attribute.
+ */
+ public Artifact getHostPrerequisiteArtifact(String attributeName) {
+ TransitiveInfoCollection target = getPrerequisite(attributeName, Mode.HOST);
+ return transitiveInfoCollectionToArtifact(attributeName, target);
+ }
+
+ private Artifact transitiveInfoCollectionToArtifact(
+ String attributeName, TransitiveInfoCollection target) {
+ if (target != null) {
+ Iterable<Artifact> artifacts = target.getProvider(FileProvider.class).getFilesToBuild();
+ if (Iterables.size(artifacts) == 1) {
+ return Iterables.getOnlyElement(artifacts);
+ } else {
+ attributeError(attributeName, target.getLabel() + " expected a single artifact");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the sole file in the "srcs" attribute. Reports an error and
+ * (possibly) returns null if "srcs" does not identify a single file of the
+ * expected type.
+ */
+ public Artifact getSingleSource(String fileTypeName) {
+ List<Artifact> srcs = PrerequisiteArtifacts.get(this, "srcs", Mode.TARGET).list();
+ switch (srcs.size()) {
+ case 0 : // error already issued by getSrc()
+ return null;
+ case 1 : // ok
+ return Iterables.getOnlyElement(srcs);
+ default :
+ attributeError("srcs", "only a single " + fileTypeName + " is allowed here");
+ return srcs.get(0);
+ }
+ }
+
+ public Artifact getSingleSource() {
+ return getSingleSource(getRule().getRuleClass() + " source file");
+ }
+
+ /**
+ * Returns a path fragment qualified by the rule name and unique fragment to
+ * disambiguate artifacts produced from the source file appearing in
+ * multiple rules.
+ *
+ * <p>For example "pkg/dir/name" -> "pkg/<fragment>/rule/dir/name.
+ */
+ public final PathFragment getUniqueDirectory(String fragment) {
+ return AnalysisUtils.getUniqueDirectory(getLabel(), new PathFragment(fragment));
+ }
+
+ /**
+ * Check that all targets that were specified as sources are from the same
+ * package as this rule. Output a warning or an error for every target that is
+ * imported from a different package.
+ */
+ public void checkSrcsSamePackage(boolean onlyWarn) {
+ PathFragment packageName = getLabel().getPackageFragment();
+ for (Artifact srcItem : PrerequisiteArtifacts.get(this, "srcs", Mode.TARGET).list()) {
+ if (!srcItem.isSourceArtifact()) {
+ // In theory, we should not do this check. However, in practice, we
+ // have a couple of rules that do not obey the "srcs must contain
+ // files and only files" rule. Thus, we are stuck with this hack here :(
+ continue;
+ }
+ Label associatedLabel = srcItem.getOwner();
+ PathFragment itemPackageName = associatedLabel.getPackageFragment();
+ if (!itemPackageName.equals(packageName)) {
+ String message = "please do not import '" + associatedLabel + "' directly. "
+ + "You should either move the file to this package or depend on "
+ + "an appropriate rule there";
+ if (onlyWarn) {
+ attributeWarning("srcs", message);
+ } else {
+ attributeError("srcs", message);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Returns the label to which the {@code NODEP_LABEL} attribute
+ * {@code attrName} refers, checking that it is a valid label, and that it is
+ * referring to a local target. Reports a warning otherwise.
+ */
+ public Label getLocalNodepLabelAttribute(String attrName) {
+ Label label = attributes().get(attrName, Type.NODEP_LABEL);
+ if (label == null) {
+ return null;
+ }
+
+ if (!getTarget().getLabel().getPackageFragment().equals(label.getPackageFragment())) {
+ attributeWarning(attrName, "does not reference a local rule");
+ }
+
+ return label;
+ }
+
+ /**
+ * Returns the implicit output artifact for a given template function. If multiple or no artifacts
+ * can be found as a result of the template, an exception is thrown.
+ */
+ public Artifact getImplicitOutputArtifact(ImplicitOutputsFunction function) {
+ Iterable<String> result;
+ try {
+ result = function.getImplicitOutputs(RawAttributeMapper.of(rule));
+ } catch (EvalException e) {
+ // It's ok as long as we don't use this method from Skylark.
+ throw new IllegalStateException(e);
+ }
+ return getImplicitOutputArtifact(Iterables.getOnlyElement(result));
+ }
+
+ /**
+ * Only use from Skylark. Returns the implicit output artifact for a given output path.
+ */
+ public Artifact getImplicitOutputArtifact(String path) {
+ Root root = getBinOrGenfilesDirectory();
+ PathFragment packageFragment = getLabel().getPackageFragment();
+ return getAnalysisEnvironment().getDerivedArtifact(packageFragment.getRelative(path), root);
+ }
+
+ /**
+ * Convenience method to return a host configured target for the "compiler"
+ * attribute. Allows caller to decide whether a warning should be printed if
+ * the "compiler" attribute is not set to the default value.
+ *
+ * @param warnIfNotDefault if true, print a warning if the value for the
+ * "compiler" attribute is set to something other than the default
+ * @return a ConfiguredTarget using the host configuration for the "compiler"
+ * attribute
+ */
+ public final FilesToRunProvider getCompiler(boolean warnIfNotDefault) {
+ Label label = attributes().get("compiler", Type.LABEL);
+ if (warnIfNotDefault && !label.equals(getRule().getAttrDefaultValue("compiler"))) {
+ attributeWarning("compiler", "setting the compiler is strongly discouraged");
+ }
+ return getExecutablePrerequisite("compiler", Mode.HOST);
+ }
+
+ /**
+ * Returns the (unmodifiable, ordered) list of artifacts which are the outputs
+ * of this target.
+ *
+ * <p>Each element in this list is associated with a single output, either
+ * declared implicitly (via setImplicitOutputsFunction()) or explicitly
+ * (listed in the 'outs' attribute of our rule).
+ */
+ public final ImmutableList<Artifact> getOutputArtifacts() {
+ ImmutableList.Builder<Artifact> artifacts = ImmutableList.builder();
+ for (OutputFile out : getRule().getOutputFiles()) {
+ artifacts.add(createOutputArtifact(out));
+ }
+ return artifacts.build();
+ }
+
+ /**
+ * Like getFilesToBuild(), except that it also includes the runfiles middleman, if any.
+ * Middlemen are expanded in the SpawnStrategy or by the Distributor.
+ */
+ public static ImmutableList<Artifact> getFilesToRun(
+ RunfilesSupport runfilesSupport, NestedSet<Artifact> filesToBuild) {
+ if (runfilesSupport == null) {
+ return ImmutableList.copyOf(filesToBuild);
+ } else {
+ ImmutableList.Builder<Artifact> allFilesToBuild = ImmutableList.builder();
+ allFilesToBuild.addAll(filesToBuild);
+ allFilesToBuild.add(runfilesSupport.getRunfilesMiddleman());
+ return allFilesToBuild.build();
+ }
+ }
+
+ /**
+ * Like {@link #getOutputArtifacts()} but for a singular output item.
+ * Reports an error if the "out" attribute is not a singleton.
+ *
+ * @return null if the output list is empty, the artifact for the first item
+ * of the output list otherwise
+ */
+ public Artifact getOutputArtifact() {
+ List<Artifact> outs = getOutputArtifacts();
+ if (outs.size() != 1) {
+ attributeError("out", "exactly one output file required");
+ if (outs.isEmpty()) {
+ return null;
+ }
+ }
+ return outs.get(0);
+ }
+
+ /**
+ * Returns an artifact with a given file extension. All other path components
+ * are the same as in {@code pathFragment}.
+ */
+ public final Artifact getRelatedArtifact(PathFragment pathFragment, String extension) {
+ PathFragment file = FileSystemUtils.replaceExtension(pathFragment, extension);
+ return getAnalysisEnvironment().getDerivedArtifact(file, getConfiguration().getBinDirectory());
+ }
+
+ /**
+ * Returns true if runfiles support should create the runfiles tree, or
+ * false if it should just create the manifest.
+ */
+ public boolean shouldCreateRunfilesSymlinks() {
+ // TODO(bazel-team): Ideally we wouldn't need such logic, and we'd
+ // always use the BuildConfiguration#buildRunfiles() to determine
+ // whether to build the runfiles. The problem is that certain build
+ // steps actually consume their runfiles. These include:
+ // a. par files consumes the runfiles directory
+ // We should modify autopar to take a list of files instead.
+ // of the runfiles directory.
+ // b. host tools could potentially use data files, but currently don't
+ // (they're run from the execution root, not a runfiles tree).
+ // Currently hostConfiguration.buildRunfiles() returns true.
+ if (TargetUtils.isTestRule(getTarget())) {
+ // Tests are only executed during testing (duh),
+ // and their runfiles are generated lazily on local
+ // execution (see LocalTestStrategy). Therefore, it
+ // is safe not to build their runfiles.
+ return getConfiguration().buildRunfiles();
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * @return true if {@code rule} is visible from {@code prerequisite}.
+ *
+ * <p>This only computes the logic as implemented by the visibility system. The final decision
+ * whether a dependency is allowed is made by
+ * {@link ConfiguredRuleClassProvider.PrerequisiteValidator}.
+ */
+ public static boolean isVisible(Rule rule, TransitiveInfoCollection prerequisite) {
+ // Check visibility attribute
+ for (PackageSpecification specification :
+ prerequisite.getProvider(VisibilityProvider.class).getVisibility()) {
+ if (specification.containsPackage(rule.getLabel().getPackageFragment())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @return the set of features applicable for the current rule's package.
+ */
+ public ImmutableSet<String> getFeatures() {
+ return features;
+ }
+
+ /**
+ * Builder class for a RuleContext.
+ */
+ public static final class Builder {
+ private final AnalysisEnvironment env;
+ private final Rule rule;
+ private final BuildConfiguration configuration;
+ private final PrerequisiteValidator prerequisiteValidator;
+ private ListMultimap<Attribute, ConfiguredTarget> prerequisiteMap;
+ private Set<ConfigMatchingProvider> configConditions;
+ private NestedSet<PackageSpecification> visibility;
+
+ Builder(AnalysisEnvironment env, Rule rule, BuildConfiguration configuration,
+ PrerequisiteValidator prerequisiteValidator) {
+ this.env = Preconditions.checkNotNull(env);
+ this.rule = Preconditions.checkNotNull(rule);
+ this.configuration = Preconditions.checkNotNull(configuration);
+ this.prerequisiteValidator = prerequisiteValidator;
+ }
+
+ RuleContext build() {
+ Preconditions.checkNotNull(prerequisiteMap);
+ Preconditions.checkNotNull(configConditions);
+ Preconditions.checkNotNull(visibility);
+ ListMultimap<String, ConfiguredTarget> targetMap = createTargetMap();
+ ListMultimap<String, ConfiguredFilesetEntry> filesetEntryMap =
+ createFilesetEntryMap(rule, configConditions);
+ return new RuleContext(this, targetMap, filesetEntryMap, configConditions,
+ getEnabledFeatures());
+ }
+
+ private ImmutableSet<String> getEnabledFeatures() {
+ Set<String> enabled = new HashSet<>();
+ Set<String> disabled = new HashSet<>();
+ for (String feature : Iterables.concat(getConfiguration().getDefaultFeatures(),
+ getRule().getPackage().getFeatures())) {
+ if (feature.startsWith("-")) {
+ disabled.add(feature.substring(1));
+ } else if (feature.equals("no_layering_check")) {
+ // TODO(bazel-team): Remove once we do not have BUILD files left that contain
+ // 'no_layering_check'.
+ disabled.add(feature.substring(3));
+ } else {
+ enabled.add(feature);
+ }
+ }
+ return Sets.difference(enabled, disabled).immutableCopy();
+ }
+
+ Builder setVisibility(NestedSet<PackageSpecification> visibility) {
+ this.visibility = visibility;
+ return this;
+ }
+
+ /**
+ * Sets the prerequisites and checks their visibility. It also generates appropriate error or
+ * warning messages and sets the error flag as appropriate.
+ */
+ Builder setPrerequisites(ListMultimap<Attribute, ConfiguredTarget> prerequisiteMap) {
+ this.prerequisiteMap = Preconditions.checkNotNull(prerequisiteMap);
+ return this;
+ }
+
+ /**
+ * Sets the configuration conditions needed to determine which paths to follow for this
+ * rule's configurable attributes.
+ */
+ Builder setConfigConditions(Set<ConfigMatchingProvider> configConditions) {
+ this.configConditions = Preconditions.checkNotNull(configConditions);
+ return this;
+ }
+
+ private boolean validateFilesetEntry(FilesetEntry filesetEntry, ConfiguredTarget src) {
+ if (src.getProvider(FilesetProvider.class) != null) {
+ return true;
+ }
+ if (filesetEntry.isSourceFileset()) {
+ return true;
+ }
+
+ Target srcTarget = src.getTarget();
+ if (!(srcTarget instanceof FileTarget)) {
+ attributeError("entries", String.format(
+ "Invalid 'srcdir' target '%s'. Must be another Fileset or package",
+ srcTarget.getLabel()));
+ return false;
+ }
+
+ if (srcTarget instanceof OutputFile) {
+ attributeWarning("entries", String.format("'srcdir' target '%s' is not an input file. "
+ + "This forces the Fileset to be executed unconditionally",
+ srcTarget.getLabel()));
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines and returns a map from attribute name to list of configured fileset entries, based
+ * on a PrerequisiteMap instance.
+ */
+ private ListMultimap<String, ConfiguredFilesetEntry> createFilesetEntryMap(
+ final Rule rule, Set<ConfigMatchingProvider> configConditions) {
+ final ImmutableSortedKeyListMultimap.Builder<String, ConfiguredFilesetEntry> mapBuilder =
+ ImmutableSortedKeyListMultimap.builder();
+ for (Attribute attr : rule.getAttributes()) {
+ if (attr.getType() != Type.FILESET_ENTRY_LIST) {
+ continue;
+ }
+ String attributeName = attr.getName();
+ Map<Label, ConfiguredTarget> ctMap = new HashMap<>();
+ for (ConfiguredTarget prerequisite : prerequisiteMap.get(attr)) {
+ ctMap.put(prerequisite.getLabel(), prerequisite);
+ }
+ List<FilesetEntry> entries = ConfiguredAttributeMapper.of(rule, configConditions)
+ .get(attributeName, Type.FILESET_ENTRY_LIST);
+ for (FilesetEntry entry : entries) {
+ if (entry.getFiles() == null) {
+ Label label = entry.getSrcLabel();
+ ConfiguredTarget src = ctMap.get(label);
+ if (!validateFilesetEntry(entry, src)) {
+ continue;
+ }
+
+ mapBuilder.put(attributeName, new ConfiguredFilesetEntry(entry, src));
+ } else {
+ ImmutableList.Builder<TransitiveInfoCollection> files = ImmutableList.builder();
+ for (Label file : entry.getFiles()) {
+ files.add(ctMap.get(file));
+ }
+ mapBuilder.put(attributeName, new ConfiguredFilesetEntry(entry, files.build()));
+ }
+ }
+ }
+ return mapBuilder.build();
+ }
+
+ /**
+ * Determines and returns a map from attribute name to list of configured targets.
+ */
+ private ImmutableSortedKeyListMultimap<String, ConfiguredTarget> createTargetMap() {
+ ImmutableSortedKeyListMultimap.Builder<String, ConfiguredTarget> mapBuilder =
+ ImmutableSortedKeyListMultimap.builder();
+
+ for (Map.Entry<Attribute, Collection<ConfiguredTarget>> entry :
+ prerequisiteMap.asMap().entrySet()) {
+ Attribute attribute = entry.getKey();
+ if (attribute == null) {
+ continue;
+ }
+ if (attribute.isSilentRuleClassFilter()) {
+ Predicate<RuleClass> filter = attribute.getAllowedRuleClassesPredicate();
+ for (ConfiguredTarget configuredTarget : entry.getValue()) {
+ Target prerequisiteTarget = configuredTarget.getTarget();
+ if ((prerequisiteTarget instanceof Rule)
+ && filter.apply(((Rule) prerequisiteTarget).getRuleClassObject())) {
+ validateDirectPrerequisite(attribute, configuredTarget);
+ mapBuilder.put(attribute.getName(), configuredTarget);
+ }
+ }
+ } else {
+ for (ConfiguredTarget configuredTarget : entry.getValue()) {
+ validateDirectPrerequisite(attribute, configuredTarget);
+ mapBuilder.put(attribute.getName(), configuredTarget);
+ }
+ }
+ }
+
+ // Handle abi_deps+deps error.
+ Attribute abiDepsAttr = rule.getAttributeDefinition("abi_deps");
+ if ((abiDepsAttr != null) && rule.isAttributeValueExplicitlySpecified("abi_deps")
+ && rule.isAttributeValueExplicitlySpecified("deps")) {
+ attributeError("deps", "Only one of deps and abi_deps should be provided");
+ }
+ return mapBuilder.build();
+ }
+
+ private String prefixRuleMessage(String message) {
+ return String.format("in %s rule %s: %s", rule.getRuleClass(), rule.getLabel(), message);
+ }
+
+ private String maskInternalAttributeNames(String name) {
+ return Attribute.isImplicit(name) ? "(an implicit dependency)" : name;
+ }
+
+ private String prefixAttributeMessage(String attrName, String message) {
+ return String.format("in %s attribute of %s rule %s: %s",
+ maskInternalAttributeNames(attrName), rule.getRuleClass(), rule.getLabel(), message);
+ }
+
+ public void reportError(Location location, String message) {
+ env.getEventHandler().handle(Event.error(location, message));
+ }
+
+ public void ruleError(String message) {
+ reportError(rule.getLocation(), prefixRuleMessage(message));
+ }
+
+ public void attributeError(String attrName, String message) {
+ reportError(rule.getAttributeLocation(attrName), prefixAttributeMessage(attrName, message));
+ }
+
+ public void reportWarning(Location location, String message) {
+ env.getEventHandler().handle(Event.warn(location, message));
+ }
+
+ public void ruleWarning(String message) {
+ env.getEventHandler().handle(Event.warn(rule.getLocation(), prefixRuleMessage(message)));
+ }
+
+ public void attributeWarning(String attrName, String message) {
+ reportWarning(rule.getAttributeLocation(attrName), prefixAttributeMessage(attrName, message));
+ }
+
+ private void reportBadPrerequisite(Attribute attribute, String targetKind,
+ Label prerequisiteLabel, String reason, boolean isWarning) {
+ String msgPrefix = targetKind != null ? targetKind + " " : "";
+ String msgReason = reason != null ? " (" + reason + ")" : "";
+ if (isWarning) {
+ attributeWarning(attribute.getName(), String.format(
+ "%s'%s' is unexpected here%s; continuing anyway",
+ msgPrefix, prerequisiteLabel, msgReason));
+ } else {
+ attributeError(attribute.getName(), String.format(
+ "%s'%s' is misplaced here%s", msgPrefix, prerequisiteLabel, msgReason));
+ }
+ }
+
+ private void validateDirectPrerequisiteType(ConfiguredTarget prerequisite,
+ Attribute attribute) {
+ Target prerequisiteTarget = prerequisite.getTarget();
+ Label prerequisiteLabel = prerequisiteTarget.getLabel();
+
+ if (prerequisiteTarget instanceof Rule) {
+ Rule prerequisiteRule = (Rule) prerequisiteTarget;
+
+ String reason = attribute.getValidityPredicate().checkValid(rule, prerequisiteRule);
+ if (reason != null) {
+ reportBadPrerequisite(attribute, prerequisiteTarget.getTargetKind(),
+ prerequisiteLabel, reason, false);
+ }
+ }
+
+ if (attribute.isStrictLabelCheckingEnabled()) {
+ if (prerequisiteTarget instanceof Rule) {
+ RuleClass ruleClass = ((Rule) prerequisiteTarget).getRuleClassObject();
+ if (!attribute.getAllowedRuleClassesPredicate().apply(ruleClass)) {
+ boolean allowedWithWarning = attribute.getAllowedRuleClassesWarningPredicate()
+ .apply(ruleClass);
+ reportBadPrerequisite(attribute, prerequisiteTarget.getTargetKind(), prerequisiteLabel,
+ "expected " + attribute.getAllowedRuleClassesPredicate().toString(),
+ allowedWithWarning);
+ }
+ } else if (prerequisiteTarget instanceof FileTarget) {
+ if (!attribute.getAllowedFileTypesPredicate()
+ .apply(((FileTarget) prerequisiteTarget).getFilename())) {
+ if (prerequisiteTarget instanceof InputFile
+ && !((InputFile) prerequisiteTarget).getPath().exists()) {
+ // Misplaced labels, no corresponding target exists
+ if (attribute.getAllowedFileTypesPredicate().isNone()
+ && !((InputFile) prerequisiteTarget).getFilename().contains(".")) {
+ // There are no allowed files in the attribute but it's not a valid rule,
+ // and the filename doesn't contain a dot --> probably a misspelled rule
+ attributeError(attribute.getName(),
+ "rule '" + prerequisiteLabel + "' does not exist");
+ } else {
+ attributeError(attribute.getName(),
+ "target '" + prerequisiteLabel + "' does not exist");
+ }
+ } else {
+ // The file exists but has a bad extension
+ reportBadPrerequisite(attribute, "file", prerequisiteLabel,
+ "expected " + attribute.getAllowedFileTypesPredicate().toString(), false);
+ }
+ }
+ }
+ }
+ }
+
+ public Rule getRule() {
+ return rule;
+ }
+
+ public BuildConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ /**
+ * @return true if {@code rule} is visible from {@code prerequisite}.
+ *
+ * <p>This only computes the logic as implemented by the visibility system. The final decision
+ * whether a dependency is allowed is made by
+ * {@link ConfiguredRuleClassProvider.PrerequisiteValidator}, who is supposed to call this
+ * method to determine whether a dependency is allowed as per visibility rules.
+ */
+ public boolean isVisible(TransitiveInfoCollection prerequisite) {
+ return RuleContext.isVisible(rule, prerequisite);
+ }
+
+ private void validateDirectPrerequisiteFileTypes(ConfiguredTarget prerequisite,
+ Attribute attribute) {
+ if (attribute.isSkipAnalysisTimeFileTypeCheck()) {
+ return;
+ }
+ FileTypeSet allowedFileTypes = attribute.getAllowedFileTypesPredicate();
+ if (allowedFileTypes == FileTypeSet.ANY_FILE && !attribute.isNonEmpty()
+ && !attribute.isSingleArtifact()) {
+ return;
+ }
+
+ // If we allow any file we still need to check if there are actually files generated
+ // Note that this check only runs for ANY_FILE predicates if the attribute is NON_EMPTY
+ // or SINGLE_ARTIFACT
+ // If we performed this check when allowedFileTypes == NO_FILE this would
+ // always throw an error in those cases
+ if (allowedFileTypes != FileTypeSet.NO_FILE) {
+ Iterable<Artifact> artifacts = prerequisite.getProvider(FileProvider.class)
+ .getFilesToBuild();
+ if (attribute.isSingleArtifact() && Iterables.size(artifacts) != 1) {
+ attributeError(attribute.getName(),
+ "'" + prerequisite.getLabel() + "' must produce a single file");
+ return;
+ }
+ for (Artifact sourceArtifact : artifacts) {
+ if (allowedFileTypes.apply(sourceArtifact.getFilename())) {
+ return;
+ }
+ }
+ attributeError(attribute.getName(), "'" + prerequisite.getLabel()
+ + "' does not produce any " + rule.getRuleClass() + " " + attribute.getName()
+ + " files (expected " + allowedFileTypes + ")");
+ }
+ }
+
+ private void validateMandatoryProviders(ConfiguredTarget prerequisite, Attribute attribute) {
+ for (String provider : attribute.getMandatoryProviders()) {
+ if (prerequisite.get(provider) == null) {
+ attributeError(attribute.getName(), "'" + prerequisite.getLabel()
+ + "' does not have mandatory provider '" + provider + "'");
+ }
+ }
+ }
+
+ private void validateDirectPrerequisite(Attribute attribute, ConfiguredTarget prerequisite) {
+ validateDirectPrerequisiteType(prerequisite, attribute);
+ validateDirectPrerequisiteFileTypes(prerequisite, attribute);
+ validateMandatoryProviders(prerequisite, attribute);
+ prerequisiteValidator.validate(this, prerequisite, attribute);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "RuleContext(" + getLabel() + ", " + getConfiguration() + ")";
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleDefinition.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleDefinition.java
new file mode 100644
index 0000000..c5e32e3
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleDefinition.java
@@ -0,0 +1,39 @@
+// 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.devtools.build.lib.packages.RuleClass;
+
+/**
+ * This class is a common ancestor for every rule object.
+ *
+ * <p>Implementors are also required to have the {@link BlazeRule} annotation
+ * set.
+ */
+public interface RuleDefinition {
+ /**
+ * This method should return a RuleClass object that represents the rule. The usual pattern is
+ * that various setter methods are called on the builder object passed in as the argument, then
+ * the object that is built by the builder is returned.
+ *
+ * @param builder A {@link com.google.devtools.build.lib.packages.RuleClass.Builder} object
+ * already preloaded with the attributes of the ancestors specified in the {@link
+ * BlazeRule} annotation.
+ * @param environment The services Blaze provides to rule definitions.
+ *
+ * @return the {@link RuleClass} representing the rule.
+ */
+ RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment);
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleDefinitionEnvironment.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleDefinitionEnvironment.java
new file mode 100644
index 0000000..4dcd080
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleDefinitionEnvironment.java
@@ -0,0 +1,31 @@
+// 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.devtools.build.lib.syntax.Label;
+
+/**
+ * Encapsulates the services available for implementors of the {@link RuleDefinition}
+ * interface.
+ */
+public interface RuleDefinitionEnvironment {
+ /**
+ * Parses the given string as a label and returns the label, by calling {@link
+ * Label#parseAbsolute}. Instead of throwing a {@link
+ * com.google.devtools.build.lib.syntax.Label.SyntaxException}, it throws an {@link
+ * IllegalArgumentException}, if the parsing fails.
+ */
+ Label getLabel(String labelValue);
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/Runfiles.java b/src/main/java/com/google/devtools/build/lib/analysis/Runfiles.java
new file mode 100644
index 0000000..a4da4b4
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/Runfiles.java
@@ -0,0 +1,756 @@
+// 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.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+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.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * An object that encapsulates runfiles. Conceptually, the runfiles are a map of paths to files,
+ * forming a symlink tree.
+ *
+ * <p>In order to reduce memory consumption, this map is not explicitly stored here, but instead as
+ * a combination of four parts: artifacts placed at their root-relative paths, source tree symlinks,
+ * root symlinks (outside of the source tree), and artifacts included as parts of "pruning
+ * manifests" (see {@link PruningManifest}).
+ */
+@Immutable
+public final class Runfiles {
+ private static final Function<Map.Entry<PathFragment, Artifact>, Artifact> TO_ARTIFACT =
+ new Function<Map.Entry<PathFragment, Artifact>, Artifact>() {
+ @Override
+ public Artifact apply(Map.Entry<PathFragment, Artifact> input) {
+ return input.getValue();
+ }
+ };
+
+ private static final Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>>
+ DUMMY_SYMLINK_EXPANDER =
+ new Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>>() {
+ @Override
+ public Map<PathFragment, Artifact> apply(Map<PathFragment, Artifact> input) {
+ return ImmutableMap.of();
+ }
+ };
+
+ // It is important to declare this *after* the DUMMY_SYMLINK_EXPANDER to avoid NPEs
+ public static final Runfiles EMPTY = new Builder().build();
+
+
+ /**
+ * The artifacts that should *always* be present in the runfiles directory. These are
+ * differentiated from the artifacts that may or may not be included by a pruning manifest
+ * (see {@link PruningManifest} below).
+ *
+ * <p>This collection may not include any middlemen. These artifacts will be placed at a location
+ * that corresponds to the root-relative path of each artifact. It's possible for several
+ * artifacts to have the same root-relative path, in which case the last one will win.
+ */
+ private final NestedSet<Artifact> unconditionalArtifacts;
+
+ /**
+ * A map of symlinks that should be present in the runfiles directory. In general, the symlink can
+ * be determined from the artifact by using the root-relative path, so this should only be used
+ * for cases where that isn't possible.
+ *
+ * <p>This may include runfiles symlinks from the root of the runfiles tree.
+ */
+ private final NestedSet<Map.Entry<PathFragment, Artifact>> symlinks;
+
+ /**
+ * A map of symlinks that should be present above the runfiles directory. These are useful for
+ * certain rule types like AppEngine apps which have root level config files outside of the
+ * regular source tree.
+ */
+ private final NestedSet<Map.Entry<PathFragment, Artifact>> rootSymlinks;
+
+ /**
+ * A function to generate extra manifest entries.
+ */
+ private final Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>>
+ manifestExpander;
+
+ /**
+ * Defines a set of artifacts that may or may not be included in the runfiles directory and
+ * a manifest file that makes that determination. These are applied on top of any artifacts
+ * specified in {@link #unconditionalArtifacts}.
+ *
+ * <p>The incentive behind this is to enable execution-phase "pruning" of runfiles. Anything
+ * set in unconditionalArtifacts is hard-set in Blaze's analysis phase, and thus unchangeable in
+ * response to execution phase results. This isn't always convenient. For example, say we have an
+ * action that consumes a set of "possible" runtime dependencies for a source file, parses that
+ * file for "import a.b.c" statements, and outputs a manifest of the actual dependencies that are
+ * referenced and thus really needed. This can reduce the size of the runfiles set, but we can't
+ * use this information until the manifest output is available.
+ *
+ * <p>Only artifacts present in the candidate set AND the manifest output make it into the
+ * runfiles tree. The candidate set requirement guarantees that analysis-time dependencies are a
+ * superset of the pruned dependencies, so undeclared inclusions (which can break build
+ * correctness) aren't possible.
+ */
+ public static class PruningManifest {
+ private final NestedSet<Artifact> candidateRunfiles;
+ private final Artifact manifestFile;
+
+ /**
+ * Creates a new pruning manifest.
+ *
+ * @param candidateRunfiles set of possible artifacts that the manifest file may reference
+ * @param manifestFile the manifest file, expected to be a newline-separated list of
+ * source tree root-relative paths (i.e. "my/package/myfile.txt"). Anything that can't be
+ * resolved back to an entry in candidateRunfiles is ignored and will *not* make it into
+ * the runfiles tree.
+ */
+ public PruningManifest(NestedSet<Artifact> candidateRunfiles, Artifact manifestFile) {
+ this.candidateRunfiles = candidateRunfiles;
+ this.manifestFile = manifestFile;
+ }
+
+ public NestedSet<Artifact> getCandidateRunfiles() {
+ return candidateRunfiles;
+ }
+
+ public Artifact getManifestFile() {
+ return manifestFile;
+ }
+ }
+
+ /**
+ * The pruning manifests that should be applied to these runfiles.
+ */
+ private final NestedSet<PruningManifest> pruningManifests;
+
+ private Runfiles(NestedSet<Artifact> artifacts,
+ NestedSet<Map.Entry<PathFragment, Artifact>> symlinks,
+ NestedSet<Map.Entry<PathFragment, Artifact>> rootSymlinks,
+ NestedSet<PruningManifest> pruningManifests,
+ Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> expander) {
+ this.unconditionalArtifacts = Preconditions.checkNotNull(artifacts);
+ this.symlinks = Preconditions.checkNotNull(symlinks);
+ this.rootSymlinks = Preconditions.checkNotNull(rootSymlinks);
+ this.pruningManifests = Preconditions.checkNotNull(pruningManifests);
+ this.manifestExpander = Preconditions.checkNotNull(expander);
+ }
+
+ /**
+ * Returns the artifacts that are unconditionally included in the runfiles (as opposed to
+ * pruning manifest candidates, which may or may not be included).
+ */
+ @VisibleForTesting
+ public NestedSet<Artifact> getUnconditionalArtifacts() {
+ return unconditionalArtifacts;
+ }
+
+ /**
+ * Returns the artifacts that are unconditionally included in the runfiles (as opposed to
+ * pruning manifest candidates, which may or may not be included). Middleman artifacts are
+ * excluded.
+ */
+ public Iterable<Artifact> getUnconditionalArtifactsWithoutMiddlemen() {
+ return Iterables.filter(unconditionalArtifacts, Artifact.MIDDLEMAN_FILTER);
+ }
+
+ /**
+ * Returns the collection of runfiles as artifacts, including both unconditional artifacts
+ * and pruning manifest candidates.
+ */
+ @VisibleForTesting
+ public NestedSet<Artifact> getArtifacts() {
+ NestedSetBuilder<Artifact> allArtifacts = NestedSetBuilder.stableOrder();
+ allArtifacts.addAll(unconditionalArtifacts.toCollection());
+ for (PruningManifest manifest : getPruningManifests()) {
+ allArtifacts.addTransitive(manifest.getCandidateRunfiles());
+ }
+ return allArtifacts.build();
+ }
+
+ /**
+ * Returns the collection of runfiles as artifacts, including both unconditional artifacts
+ * and pruning manifest candidates. Middleman artifacts are excluded.
+ */
+ public Iterable<Artifact> getArtifactsWithoutMiddlemen() {
+ return Iterables.filter(getArtifacts(), Artifact.MIDDLEMAN_FILTER);
+ }
+
+ /**
+ * Returns the symlinks.
+ */
+ public NestedSet<Map.Entry<PathFragment, Artifact>> getSymlinks() {
+ return symlinks;
+ }
+
+ /**
+ * Returns the symlinks as a map from path fragment to artifact.
+ */
+ public Map<PathFragment, Artifact> getSymlinksAsMap() {
+ return entriesToMap(symlinks);
+ }
+
+ /**
+ * @param eventHandler Used for throwing an error if we have an obscuring runlink.
+ * May be null, in which case obscuring symlinks are silently discarded.
+ * @param location Location for reporter. Ignored if reporter is null.
+ * @param workingManifest Manifest to be checked for obscuring symlinks.
+ * @return map of source file names mapped to their location on disk.
+ */
+ public static Map<PathFragment, Artifact> filterListForObscuringSymlinks(
+ EventHandler eventHandler, Location location, Map<PathFragment, Artifact> workingManifest) {
+ Map<PathFragment, Artifact> newManifest = new HashMap<>();
+
+ outer:
+ for (Iterator<Entry<PathFragment, Artifact>> i = workingManifest.entrySet().iterator();
+ i.hasNext(); ) {
+ Entry<PathFragment, Artifact> entry = i.next();
+ PathFragment source = entry.getKey();
+ Artifact symlink = entry.getValue();
+ // drop nested entries; warn if this changes anything
+ int n = source.segmentCount();
+ for (int j = 1; j < n; ++j) {
+ PathFragment prefix = source.subFragment(0, n - j);
+ Artifact ancestor = workingManifest.get(prefix);
+ if (ancestor != null) {
+ // This is an obscuring symlink, so just drop it and move on if there's no reporter.
+ if (eventHandler == null) {
+ continue outer;
+ }
+ PathFragment suffix = source.subFragment(n - j, n);
+ Path viaAncestor = ancestor.getPath().getRelative(suffix);
+ Path expected = symlink.getPath();
+ if (!viaAncestor.equals(expected)) {
+ eventHandler.handle(Event.warn(location, "runfiles symlink " + source + " -> "
+ + expected + " obscured by " + prefix + " -> " + ancestor.getPath()));
+ }
+ continue outer;
+ }
+ }
+ newManifest.put(entry.getKey(), entry.getValue());
+ }
+ return newManifest;
+ }
+
+ /**
+ * Returns the symlinks as a map from PathFragment to Artifact, with PathFragments relativized
+ * and rooted at the specified points.
+ * @param root The root the PathFragment is computed relative to (before it is
+ * rooted again). May be null.
+ * @param eventHandler Used for throwing an error if we have an obscuring runlink.
+ * May be null, in which case obscuring symlinks are silently discarded.
+ * @param location Location for eventHandler warnings. Ignored if eventHandler is null.
+ * @return Pair of Maps from remote path fragment to artifact, the first of normal source tree
+ * entries, the second of any elements that live outside the source tree.
+ */
+ public Pair<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> getRunfilesInputs(
+ PathFragment root, String workspaceSuffix, EventHandler eventHandler, Location location)
+ throws IOException {
+ Map<PathFragment, Artifact> manifest = getSymlinksAsMap();
+ // Add unconditional artifacts (committed to inclusion on construction of runfiles).
+ for (Artifact artifact : getUnconditionalArtifactsWithoutMiddlemen()) {
+ addToManifest(manifest, artifact, root);
+ }
+
+ // Add conditional artifacts (only included if they appear in a pruning manifest).
+ for (Runfiles.PruningManifest pruningManifest : getPruningManifests()) {
+ // This map helps us convert from source tree root-relative paths back to artifacts.
+ Map<String, Artifact> allowedRunfiles = new HashMap<>();
+ for (Artifact artifact : pruningManifest.getCandidateRunfiles()) {
+ allowedRunfiles.put(artifact.getRootRelativePath().getPathString(), artifact);
+ }
+ BufferedReader reader = new BufferedReader(
+ new InputStreamReader(pruningManifest.getManifestFile().getPath().getInputStream()));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ Artifact artifact = allowedRunfiles.get(line);
+ if (artifact != null) {
+ addToManifest(manifest, artifact, root);
+ }
+ }
+ }
+
+ manifest = filterListForObscuringSymlinks(eventHandler, location, manifest);
+ manifest.putAll(manifestExpander.apply(manifest));
+ PathFragment path = new PathFragment(workspaceSuffix);
+ Map<PathFragment, Artifact> result = new HashMap<>();
+ for (Map.Entry<PathFragment, Artifact> entry : manifest.entrySet()) {
+ result.put(path.getRelative(entry.getKey()), entry.getValue());
+ }
+ return Pair.of(result, (Map<PathFragment, Artifact>) new HashMap<>(getRootSymlinksAsMap()));
+ }
+
+ @VisibleForTesting
+ protected static void addToManifest(Map<PathFragment, Artifact> manifest, Artifact artifact,
+ PathFragment root) {
+ PathFragment rootRelativePath = root != null
+ ? artifact.getRootRelativePath().relativeTo(root)
+ : artifact.getRootRelativePath();
+ manifest.put(rootRelativePath, artifact);
+ }
+
+ /**
+ * Returns the root symlinks.
+ */
+ public NestedSet<Map.Entry<PathFragment, Artifact>> getRootSymlinks() {
+ return rootSymlinks;
+ }
+
+ /**
+ * Returns the root symlinks.
+ */
+ public Map<PathFragment, Artifact> getRootSymlinksAsMap() {
+ return entriesToMap(rootSymlinks);
+ }
+
+ /**
+ * Returns the unified map of path fragments to artifacts, taking both artifacts and symlinks into
+ * account.
+ */
+ public Map<PathFragment, Artifact> asMapWithoutRootSymlinks() {
+ Map<PathFragment, Artifact> result = entriesToMap(symlinks);
+ // If multiple artifacts have the same root-relative path, the last one in the list will win.
+ // That is because the runfiles tree cannot contain the same artifact for different
+ // configurations, because it only uses root-relative paths.
+ for (Artifact artifact : Iterables.filter(unconditionalArtifacts, Artifact.MIDDLEMAN_FILTER)) {
+ result.put(artifact.getRootRelativePath(), artifact);
+ }
+ return result;
+ }
+
+ /**
+ * Returns the pruning manifests specified for this runfiles tree.
+ */
+ public NestedSet<PruningManifest> getPruningManifests() {
+ return pruningManifests;
+ }
+
+ /**
+ * Returns the symlinks expander specified for this runfiles tree.
+ */
+ public Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> getSymlinkExpander() {
+ return manifestExpander;
+ }
+
+ /**
+ * Returns the unified map of path fragments to artifacts, taking into account artifacts,
+ * symlinks, and pruning manifest candidates. The returned set is guaranteed to be a (not
+ * necessarily strict) superset of the actual runfiles tree created at execution time.
+ */
+ public NestedSet<Artifact> getAllArtifacts() {
+ if (isEmpty()) {
+ return NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+ }
+ NestedSetBuilder<Artifact> allArtifacts = NestedSetBuilder.stableOrder();
+ allArtifacts
+ .addTransitive(unconditionalArtifacts)
+ .addAll(Iterables.transform(symlinks, TO_ARTIFACT))
+ .addAll(Iterables.transform(rootSymlinks, TO_ARTIFACT));
+ for (PruningManifest manifest : getPruningManifests()) {
+ allArtifacts.addTransitive(manifest.getCandidateRunfiles());
+ }
+ return allArtifacts.build();
+ }
+
+ /**
+ * Returns if there are no runfiles.
+ */
+ public boolean isEmpty() {
+ return unconditionalArtifacts.isEmpty() && symlinks.isEmpty() && rootSymlinks.isEmpty() &&
+ pruningManifests.isEmpty();
+ }
+
+ private static <K, V> Map<K, V> entriesToMap(Iterable<Map.Entry<K, V>> entrySet) {
+ Map<K, V> map = new LinkedHashMap<>();
+ for (Map.Entry<K, V> entry : entrySet) {
+ map.put(entry.getKey(), entry.getValue());
+ }
+ return map;
+ }
+
+ /**
+ * Builder for Runfiles objects.
+ */
+ public static final class Builder {
+ /**
+ * This must be COMPILE_ORDER because {@link #asMapWithoutRootSymlinks} overwrites earlier
+ * entries with later ones, so we want a post-order iteration.
+ */
+ private NestedSetBuilder<Artifact> artifactsBuilder =
+ NestedSetBuilder.compileOrder();
+ private NestedSetBuilder<Map.Entry<PathFragment, Artifact>> symlinksBuilder =
+ NestedSetBuilder.stableOrder();
+ private NestedSetBuilder<Map.Entry<PathFragment, Artifact>> rootSymlinksBuilder =
+ NestedSetBuilder.stableOrder();
+ private NestedSetBuilder<PruningManifest> pruningManifestsBuilder =
+ NestedSetBuilder.stableOrder();
+ private Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>>
+ manifestExpander = DUMMY_SYMLINK_EXPANDER;
+
+ /**
+ * Builds a new Runfiles object.
+ */
+ public Runfiles build() {
+ return new Runfiles(artifactsBuilder.build(), symlinksBuilder.build(),
+ rootSymlinksBuilder.build(), pruningManifestsBuilder.build(),
+ manifestExpander);
+ }
+
+ /**
+ * Adds an artifact to the internal collection of artifacts.
+ */
+ public Builder addArtifact(Artifact artifact) {
+ Preconditions.checkNotNull(artifact);
+ artifactsBuilder.add(artifact);
+ return this;
+ }
+
+ /**
+ * Adds several artifacts to the internal collection.
+ */
+ public Builder addArtifacts(Iterable<Artifact> artifacts) {
+ for (Artifact artifact : artifacts) {
+ addArtifact(artifact);
+ }
+ return this;
+ }
+
+
+ /**
+ * Use {@link #addTransitiveArtifacts} instead, to prevent increased memory use.
+ */
+ @Deprecated
+ public Builder addArtifacts(NestedSet<Artifact> artifacts) {
+ // Do not delete this method, or else addArtifacts(Iterable) calls with a NestedSet argument
+ // will not be flagged.
+ Iterable<Artifact> it = artifacts;
+ addArtifacts(it);
+ return this;
+ }
+ /**
+ * Adds a nested set to the internal collection.
+ */
+ public Builder addTransitiveArtifacts(NestedSet<Artifact> artifacts) {
+ artifactsBuilder.addTransitive(artifacts);
+ return this;
+ }
+
+ /**
+ * Adds a symlink.
+ */
+ public Builder addSymlink(PathFragment link, Artifact target) {
+ Preconditions.checkNotNull(link);
+ Preconditions.checkNotNull(target);
+ symlinksBuilder.add(Maps.immutableEntry(link, target));
+ return this;
+ }
+
+ /**
+ * Adds several symlinks.
+ */
+ public Builder addSymlinks(Map<PathFragment, Artifact> symlinks) {
+ symlinksBuilder.addAll(symlinks.entrySet());
+ return this;
+ }
+
+ /**
+ * Adds several symlinks as a NestedSet.
+ */
+ public Builder addSymlinks(NestedSet<Map.Entry<PathFragment, Artifact>> symlinks) {
+ symlinksBuilder.addTransitive(symlinks);
+ return this;
+ }
+
+ /**
+ * Adds several root symlinks.
+ */
+ public Builder addRootSymlinks(Map<PathFragment, Artifact> symlinks) {
+ rootSymlinksBuilder.addAll(symlinks.entrySet());
+ return this;
+ }
+
+ /**
+ * Adds several root symlinks as a NestedSet.
+ */
+ public Builder addRootSymlinks(NestedSet<Map.Entry<PathFragment, Artifact>> symlinks) {
+ rootSymlinksBuilder.addTransitive(symlinks);
+ return this;
+ }
+
+ /**
+ * Adds a pruning manifest. See {@link PruningManifest} for an explanation.
+ */
+ public Builder addPruningManifest(PruningManifest manifest) {
+ pruningManifestsBuilder.add(manifest);
+ return this;
+ }
+
+ /**
+ * Adds several pruning manifests as a NestedSet. See {@link PruningManifest} for an
+ * explanation.
+ */
+ public Builder addPruningManifests(NestedSet<PruningManifest> manifests) {
+ pruningManifestsBuilder.addTransitive(manifests);
+ return this;
+ }
+
+ /**
+ * Specify a function that can create additional manifest entries based on the input entries.
+ */
+ public Builder setManifestExpander(
+ Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> expander) {
+ manifestExpander = Preconditions.checkNotNull(expander);
+ return this;
+ }
+
+ /**
+ * Merges runfiles from a given runfiles support.
+ *
+ * @param runfilesSupport the runfiles support to be merged in
+ */
+ public Builder merge(@Nullable RunfilesSupport runfilesSupport) {
+ return merge(runfilesSupport, null);
+ }
+
+ /**
+ * Merges runfiles from a given runfiles support.
+ *
+ * <p>Sometimes a particular symlink from the runfiles support must not be included in runfiles.
+ * In such cases the path fragment denoting the symlink should be passed in as {@code
+ * ommittedAdditionalSymlink}. The symlink will then be filtered away from the set of additional
+ * symlinks of the target.
+ *
+ * @param runfilesSupport the runfiles support to be merged in
+ * @param omittedAdditionalSymlink the symlink to be omitted, or null if no filtering is needed
+ */
+ public Builder merge(@Nullable RunfilesSupport runfilesSupport,
+ @Nullable final PathFragment omittedAdditionalSymlink) {
+ if (runfilesSupport == null) {
+ return this;
+ }
+ // TODO(bazel-team): We may be able to remove this now.
+ addArtifact(runfilesSupport.getRunfilesMiddleman());
+ Runfiles runfiles = runfilesSupport.getRunfiles();
+ if (omittedAdditionalSymlink == null) {
+ merge(runfiles);
+ } else {
+ artifactsBuilder.addTransitive(runfiles.getUnconditionalArtifacts());
+ symlinksBuilder.addAll(Maps.filterKeys(entriesToMap(runfiles.getSymlinks()),
+ Predicates.not(Predicates.equalTo(omittedAdditionalSymlink))).entrySet());
+ rootSymlinksBuilder.addTransitive(runfiles.getRootSymlinks());
+ pruningManifestsBuilder.addTransitive(runfiles.getPruningManifests());
+ if (manifestExpander == DUMMY_SYMLINK_EXPANDER) {
+ manifestExpander = runfiles.getSymlinkExpander();
+ } else {
+ Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> otherExpander =
+ runfiles.getSymlinkExpander();
+ Preconditions.checkState((otherExpander == DUMMY_SYMLINK_EXPANDER)
+ || manifestExpander.equals(otherExpander));
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Adds the runfiles for a particular target and visits the transitive closure of "srcs",
+ * "deps" and "data", collecting all of their respective runfiles.
+ */
+ public Builder addRunfiles(RuleContext ruleContext,
+ Function<TransitiveInfoCollection, Runfiles> mapping) {
+ Preconditions.checkNotNull(mapping);
+ Preconditions.checkNotNull(ruleContext);
+ addDataDeps(ruleContext);
+ addNonDataDeps(ruleContext, mapping);
+ return this;
+ }
+
+ /**
+ * Adds the files specified by a mapping from the transitive info collection to the runfiles.
+ *
+ * <p>Dependencies in {@code srcs} and {@code deps} are considered.
+ */
+ public Builder add(RuleContext ruleContext,
+ Function<TransitiveInfoCollection, Runfiles> mapping) {
+ Preconditions.checkNotNull(ruleContext);
+ Preconditions.checkNotNull(mapping);
+ for (TransitiveInfoCollection dep : getNonDataDeps(ruleContext)) {
+ Runfiles runfiles = mapping.apply(dep);
+ if (runfiles != null) {
+ merge(runfiles);
+ }
+ }
+
+ return this;
+ }
+
+ /**
+ * Collects runfiles from data dependencies of a target.
+ */
+ public Builder addDataDeps(RuleContext ruleContext) {
+ addTargets(getPrerequisites(ruleContext, "data", Mode.DATA),
+ RunfilesProvider.DATA_RUNFILES);
+ return this;
+ }
+
+ /**
+ * Collects runfiles from "srcs" and "deps" of a target.
+ */
+ public Builder addNonDataDeps(RuleContext ruleContext,
+ Function<TransitiveInfoCollection, Runfiles> mapping) {
+ for (TransitiveInfoCollection target : getNonDataDeps(ruleContext)) {
+ addTargetExceptFileTargets(target, mapping);
+ }
+ return this;
+ }
+
+ public Builder addTargets(Iterable<? extends TransitiveInfoCollection> targets,
+ Function<TransitiveInfoCollection, Runfiles> mapping) {
+ for (TransitiveInfoCollection target : targets) {
+ addTarget(target, mapping);
+ }
+ return this;
+ }
+
+ public Builder addTarget(TransitiveInfoCollection target,
+ Function<TransitiveInfoCollection, Runfiles> mapping) {
+ return addTargetIncludingFileTargets(target, mapping);
+ }
+
+ private Builder addTargetExceptFileTargets(TransitiveInfoCollection target,
+ Function<TransitiveInfoCollection, Runfiles> mapping) {
+ Runfiles runfiles = mapping.apply(target);
+ if (runfiles != null) {
+ merge(runfiles);
+ }
+
+ return this;
+ }
+
+ private Builder addTargetIncludingFileTargets(TransitiveInfoCollection target,
+ Function<TransitiveInfoCollection, Runfiles> mapping) {
+ if (target.getProvider(RunfilesProvider.class) == null
+ && mapping == RunfilesProvider.DATA_RUNFILES) {
+ // RuleConfiguredTarget implements RunfilesProvider, so this will only be called on
+ // FileConfiguredTarget instances.
+ // TODO(bazel-team): This is a terrible hack. We should be able to make this go away
+ // by implementing RunfilesProvider on FileConfiguredTarget. We'd need to be mindful
+ // of the memory use, though, since we have a whole lot of FileConfiguredTarget instances.
+ addTransitiveArtifacts(target.getProvider(FileProvider.class).getFilesToBuild());
+ return this;
+ }
+
+ return addTargetExceptFileTargets(target, mapping);
+ }
+
+ /**
+ * Adds symlinks to given artifacts at their exec paths.
+ */
+ public Builder addSymlinksToArtifacts(Iterable<Artifact> artifacts) {
+ for (Artifact artifact : artifacts) {
+ addSymlink(artifact.getExecPath(), artifact);
+ }
+ return this;
+ }
+
+ /**
+ * Add the other {@link Runfiles} object transitively.
+ */
+ public Builder merge(Runfiles runfiles) {
+ return merge(runfiles, true);
+ }
+
+ /**
+ * Add the other {@link Runfiles} object transitively, but don't merge
+ * pruning manifests.
+ */
+ public Builder mergeExceptPruningManifests(Runfiles runfiles) {
+ return merge(runfiles, false);
+ }
+
+ /**
+ * Add the other {@link Runfiles} object transitively, with the option to include or exclude
+ * pruning manifests in the merge.
+ */
+ private Builder merge(Runfiles runfiles, boolean includePruningManifests) {
+ artifactsBuilder.addTransitive(runfiles.getUnconditionalArtifacts());
+ symlinksBuilder.addTransitive(runfiles.getSymlinks());
+ rootSymlinksBuilder.addTransitive(runfiles.getRootSymlinks());
+ if (includePruningManifests) {
+ pruningManifestsBuilder.addTransitive(runfiles.getPruningManifests());
+ }
+ if (manifestExpander == DUMMY_SYMLINK_EXPANDER) {
+ manifestExpander = runfiles.getSymlinkExpander();
+ } else {
+ Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> otherExpander =
+ runfiles.getSymlinkExpander();
+ Preconditions.checkState((otherExpander == DUMMY_SYMLINK_EXPANDER)
+ || manifestExpander.equals(otherExpander));
+ }
+ return this;
+ }
+
+ private static Iterable<TransitiveInfoCollection> getNonDataDeps(RuleContext ruleContext) {
+ return Iterables.concat(
+ // TODO(bazel-team): This line shouldn't be here. Removing it requires that no rules have
+ // dependent rules in srcs (except for filegroups and such), but always in deps.
+ // TODO(bazel-team): DONT_CHECK is not optimal here. Rules that use split configs need to
+ // be changed not to call into here.
+ getPrerequisites(ruleContext, "srcs", Mode.DONT_CHECK),
+ getPrerequisites(ruleContext, "deps", Mode.DONT_CHECK));
+ }
+
+ /**
+ * For the specified attribute "attributeName" (which must be of type list(label)), resolves all
+ * the labels into ConfiguredTargets (for the same configuration as this one) and returns them
+ * as a list.
+ *
+ * <p>If the rule does not have the specified attribute, returns the empty list.
+ */
+ private static Iterable<? extends TransitiveInfoCollection> getPrerequisites(
+ RuleContext ruleContext, String attributeName, Mode mode) {
+ if (ruleContext.getRule().isAttrDefined(attributeName, Type.LABEL_LIST)) {
+ return ruleContext.getPrerequisites(attributeName, mode);
+ } else {
+ return Collections.emptyList();
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RunfilesProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/RunfilesProvider.java
new file mode 100644
index 0000000..2e26bbc
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RunfilesProvider.java
@@ -0,0 +1,91 @@
+// 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.Function;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+/**
+ * Runfiles a target contributes to targets that depend on it.
+ *
+ * <p>The set of runfiles contributed can be different if the dependency is through a
+ * <code>data</code> attribute (note that this is just a rough approximation of the reality --
+ * rule implementations are free to request the data runfiles at any time)
+ */
+@Immutable
+public final class RunfilesProvider implements TransitiveInfoProvider {
+ private final Runfiles defaultRunfiles;
+ private final Runfiles dataRunfiles;
+
+ private RunfilesProvider(Runfiles defaultRunfiles, Runfiles dataRunfiles) {
+ this.defaultRunfiles = defaultRunfiles;
+ this.dataRunfiles = dataRunfiles;
+ }
+
+ public Runfiles getDefaultRunfiles() {
+ return defaultRunfiles;
+ }
+
+ public Runfiles getDataRunfiles() {
+ return dataRunfiles;
+ }
+
+ /**
+ * Returns a function that gets the default runfiles from a {@link TransitiveInfoCollection} or
+ * the empty runfiles instance if it does not contain that provider.
+ */
+ public static final Function<TransitiveInfoCollection, Runfiles> DEFAULT_RUNFILES =
+ new Function<TransitiveInfoCollection, Runfiles>() {
+ @Override
+ public Runfiles apply(TransitiveInfoCollection input) {
+ RunfilesProvider provider = input.getProvider(RunfilesProvider.class);
+ if (provider != null) {
+ return provider.getDefaultRunfiles();
+ }
+
+ return Runfiles.EMPTY;
+ }
+ };
+
+ /**
+ * Returns a function that gets the data runfiles from a {@link TransitiveInfoCollection} or the
+ * empty runfiles instance if it does not contain that provider.
+ *
+ * <p>These are usually used if the target is depended on through a {@code data} attribute.
+ */
+ public static final Function<TransitiveInfoCollection, Runfiles> DATA_RUNFILES =
+ new Function<TransitiveInfoCollection, Runfiles>() {
+ @Override
+ public Runfiles apply(TransitiveInfoCollection input) {
+ RunfilesProvider provider = input.getProvider(RunfilesProvider.class);
+ if (provider != null) {
+ return provider.getDataRunfiles();
+ }
+
+ return Runfiles.EMPTY;
+ }
+ };
+
+ public static RunfilesProvider simple(Runfiles defaultRunfiles) {
+ return new RunfilesProvider(defaultRunfiles, defaultRunfiles);
+ }
+
+ public static RunfilesProvider withData(
+ Runfiles defaultRunfiles, Runfiles dataRunfiles) {
+ return new RunfilesProvider(defaultRunfiles, dataRunfiles);
+ }
+
+ public static final RunfilesProvider EMPTY = new RunfilesProvider(
+ Runfiles.EMPTY, Runfiles.EMPTY);
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RunfilesSupport.java b/src/main/java/com/google/devtools/build/lib/analysis/RunfilesSupport.java
new file mode 100644
index 0000000..aa7f429
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RunfilesSupport.java
@@ -0,0 +1,382 @@
+// 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.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.SourceManifestAction.ManifestType;
+import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.RunUnder;
+import com.google.devtools.build.lib.collect.IterablesChain;
+import com.google.devtools.build.lib.packages.TargetUtils;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This class manages the creation of the runfiles symlink farms.
+ *
+ * <p>For executables that might depend on the existence of files at run-time, we create a symlink
+ * farm: a directory which contains symlinks to the right locations for those runfiles.
+ *
+ * <p>The runfiles symlink farm serves two purposes. The first is to allow programs (and
+ * programmers) to refer to files using their workspace-relative paths, regardless of whether the
+ * files were source files or generated files, and regardless of which part of the package path they
+ * came from. The second purpose is to ensure that all run-time dependencies are explicitly declared
+ * in the BUILD files; programs may only use files which the build system knows that they depend on.
+ *
+ * <p>The symlink farm contains a MANIFEST file which describes its contents. The MANIFEST file
+ * lists the names and contents of all of the symlinks in the symlink farm. For efficiency, Blaze's
+ * dependency analysis ignores the actual symlinks and just looks at the MANIFEST file. It is an
+ * invariant that the MANIFEST file should accurately represent the contents of the symlinks
+ * whenever the MANIFEST file is present. build_runfile_links.py preserves this invariant (modulo
+ * bugs - currently it has a bug where it may fail to preserve that invariant if it gets
+ * interrupted). So the Blaze dependency analysis looks only at the MANIFEST file, rather than at
+ * the individual symlinks.
+ *
+ * <p>We create an Artifact for the MANIFEST file and a RunfilesAction Action to create it. This
+ * action does not depend on any other Artifacts.
+ *
+ * <p>When building an executable and running it, there are three things which must be built: the
+ * executable itself, the runfiles symlink farm (represented in the action graph by the Artifact for
+ * its MANIFEST), and the files pointed to by the symlinks in the symlink farm. To avoid redundancy
+ * in the dependency analysis, we create a Middleman Artifact which depends on all of these. Actions
+ * which will run an executable should depend on this Middleman Artifact.
+ */
+public class RunfilesSupport {
+ private static final String RUNFILES_DIR_EXT = ".runfiles";
+
+ private final Runfiles runfiles;
+
+ private final Artifact runfilesInputManifest;
+ private final Artifact runfilesManifest;
+ private final Artifact runfilesMiddleman;
+ private final Artifact sourcesManifest;
+ private final Artifact owningExecutable;
+ private final boolean createSymlinks;
+ private final ImmutableList<String> args;
+
+ /**
+ * Creates the RunfilesSupport helper with the given executable and runfiles.
+ *
+ * @param ruleContext the rule context to create the runfiles support for
+ * @param executable the executable for whose runfiles this runfiles support is responsible, may
+ * be null
+ * @param runfiles the runfiles
+ * @param appendingArgs to be added after the rule's args
+ */
+ private RunfilesSupport(RuleContext ruleContext, Artifact executable, Runfiles runfiles,
+ List<String> appendingArgs, boolean createSymlinks) {
+ owningExecutable = executable;
+ this.createSymlinks = createSymlinks;
+
+ // Adding run_under target to the runfiles manifest so it would become part
+ // of runfiles tree and would be executable everywhere.
+ RunUnder runUnder = ruleContext.getConfiguration().getRunUnder();
+ if (runUnder != null && runUnder.getLabel() != null
+ && TargetUtils.isTestRule(ruleContext.getRule())) {
+ TransitiveInfoCollection runUnderTarget =
+ ruleContext.getPrerequisite(":run_under", Mode.DATA);
+ runfiles = new Runfiles.Builder()
+ .merge(getRunfiles(runUnderTarget))
+ .merge(runfiles)
+ .build();
+ }
+ this.runfiles = runfiles;
+
+ Preconditions.checkState(!runfiles.isEmpty());
+
+ Map<PathFragment, Artifact> symlinks = getRunfilesSymlinks();
+ if (executable != null && !symlinks.values().contains(executable)) {
+ throw new IllegalStateException("main program " + executable + " not included in runfiles");
+ }
+
+ runfilesInputManifest = createRunfilesInputManifestArtifact(ruleContext);
+ this.runfilesManifest = createRunfilesAction(ruleContext, runfiles);
+ this.runfilesMiddleman = createRunfilesMiddleman(ruleContext, runfiles.getAllArtifacts());
+ sourcesManifest = createSourceManifest(ruleContext, runfiles);
+ args = ImmutableList.<String>builder()
+ .addAll(ruleContext.getTokenizedStringListAttr("args"))
+ .addAll(appendingArgs)
+ .build();
+ }
+
+ private RunfilesSupport(Runfiles runfiles, Artifact runfilesInputManifest,
+ Artifact runfilesManifest, Artifact runfilesMiddleman, Artifact sourcesManifest,
+ Artifact owningExecutable, boolean createSymlinks, ImmutableList<String> args) {
+ this.runfiles = runfiles;
+ this.runfilesInputManifest = runfilesInputManifest;
+ this.runfilesManifest = runfilesManifest;
+ this.runfilesMiddleman = runfilesMiddleman;
+ this.sourcesManifest = sourcesManifest;
+ this.owningExecutable = owningExecutable;
+ this.createSymlinks = createSymlinks;
+ this.args = args;
+ }
+
+ /**
+ * Returns the executable owning this RunfilesSupport. Only use from Skylark.
+ */
+ public Artifact getExecutable() {
+ return owningExecutable;
+ }
+
+ /**
+ * Returns the exec path of the directory where the runfiles contained in this
+ * RunfilesSupport are generated. When the owning rule has no executable,
+ * returns null.
+ */
+ public PathFragment getRunfilesDirectoryExecPath() {
+ if (owningExecutable == null) {
+ return null;
+ }
+
+ PathFragment executablePath = owningExecutable.getExecPath();
+ return executablePath.getParentDirectory().getChild(
+ executablePath.getBaseName() + RUNFILES_DIR_EXT);
+ }
+
+ public Runfiles getRunfiles() {
+ return runfiles;
+ }
+
+ /**
+ * For executable programs, the .runfiles_manifest file outside of the
+ * runfiles symlink farm; otherwise, returns null.
+ *
+ * <p>The MANIFEST file represents the contents of all of the symlinks in the
+ * symlink farm. For efficiency, Blaze's dependency analysis ignores the
+ * actual symlinks and just looks at the MANIFEST file. It is an invariant
+ * that the MANIFEST file should accurately represent the contents of the
+ * symlinks whenever the MANIFEST file is present.
+ */
+ public Artifact getRunfilesInputManifest() {
+ return runfilesInputManifest;
+ }
+
+ private Artifact createRunfilesInputManifestArtifact(ActionConstructionContext context) {
+ // The executable may be null for emptyRunfiles
+ PathFragment relativePath = (owningExecutable != null)
+ ? owningExecutable.getRootRelativePath()
+ : Util.getWorkspaceRelativePath(context.getRule());
+ String basename = relativePath.getBaseName();
+ PathFragment inputManifestPath = relativePath.replaceName(basename + ".runfiles_manifest");
+ return context.getAnalysisEnvironment().getDerivedArtifact(inputManifestPath,
+ context.getConfiguration().getBinDirectory());
+ }
+
+ /**
+ * For executable programs, returns the MANIFEST file in the runfiles
+ * symlink farm, if blaze is run with --build_runfile_links; returns
+ * the .runfiles_manifest file outside of the symlink farm, if blaze
+ * is run with --nobuild_runfile_links.
+ * <p>
+ * Beware: In most cases {@link #getRunfilesInputManifest} is the more
+ * appropriate function.
+ */
+ public Artifact getRunfilesManifest() {
+ return runfilesManifest;
+ }
+
+ /**
+ * For executable programs, the root directory of the runfiles symlink farm;
+ * otherwise, returns null.
+ */
+ public Path getRunfilesDirectory() {
+ return FileSystemUtils.replaceExtension(getRunfilesInputManifest().getPath(), RUNFILES_DIR_EXT);
+ }
+
+ /**
+ * Returns the files pointed to by the symlinks in the runfiles symlink farm. This method is slow.
+ */
+ @VisibleForTesting
+ public Collection<Artifact> getRunfilesSymlinkTargets() {
+ return getRunfilesSymlinks().values();
+ }
+
+ /**
+ * Returns the names of the symlinks in the runfiles symlink farm as a Set of PathFragments. This
+ * method is slow.
+ */
+ // We should make this VisibleForTesting, but it is still used by TestHelper
+ public Set<PathFragment> getRunfilesSymlinkNames() {
+ return getRunfilesSymlinks().keySet();
+ }
+
+ /**
+ * Returns the names of the symlinks in the runfiles symlink farm as a Set of PathFragments. This
+ * method is slow.
+ */
+ @VisibleForTesting
+ public Map<PathFragment, Artifact> getRunfilesSymlinks() {
+ return runfiles.asMapWithoutRootSymlinks();
+ }
+
+ /**
+ * Returns both runfiles artifacts and "conditional" artifacts that may be part of a
+ * Runfiles PruningManifest. This means the returned set may be an overapproximation of the
+ * actual set of runfiles (see {@link Runfiles.PruningManifest}).
+ */
+ public Iterable<Artifact> getRunfilesArtifactsWithoutMiddlemen() {
+ return runfiles.getArtifactsWithoutMiddlemen();
+ }
+
+ /**
+ * Returns the middleman artifact that depends on getExecutable(),
+ * getRunfilesManifest(), and getRunfilesSymlinkTargets(). Anything which
+ * needs to actually run the executable should depend on this.
+ */
+ public Artifact getRunfilesMiddleman() {
+ return runfilesMiddleman;
+ }
+
+ /**
+ * Returns the Sources manifest.
+ * This may be null if the owningRule has no executable.
+ */
+ public Artifact getSourceManifest() {
+ return sourcesManifest;
+ }
+
+ private Artifact createRunfilesMiddleman(ActionConstructionContext context,
+ Iterable<Artifact> allRunfilesArtifacts) {
+ Iterable<Artifact> inputs = IterablesChain.<Artifact>builder()
+ .add(allRunfilesArtifacts)
+ .addElement(runfilesManifest)
+ .build();
+ return context.getAnalysisEnvironment().getMiddlemanFactory().createRunfilesMiddleman(
+ context.getActionOwner(), owningExecutable, inputs,
+ context.getConfiguration().getMiddlemanDirectory());
+ }
+
+ /**
+ * Creates a runfiles action for all of the specified files, and returns the
+ * output artifact (the artifact for the MANIFEST file).
+ *
+ * <p>The "runfiles" action creates a symlink farm that links all the runfiles
+ * (which may come from different places, e.g. different package paths,
+ * generated files, etc.) into a single tree, so that programs can access them
+ * using the workspace-relative name.
+ */
+ private Artifact createRunfilesAction(ActionConstructionContext context, Runfiles runfiles) {
+ // Compute the names of the runfiles directory and its MANIFEST file.
+ Artifact inputManifest = getRunfilesInputManifest();
+ context.getAnalysisEnvironment().registerAction(
+ SourceManifestAction.forRunfiles(
+ ManifestType.SOURCE_SYMLINKS, context.getActionOwner(), inputManifest, runfiles));
+
+ if (!createSymlinks) {
+ // Just return the manifest if that's all the build calls for.
+ return inputManifest;
+ }
+
+ PathFragment runfilesDir = FileSystemUtils.replaceExtension(inputManifest.getRootRelativePath(),
+ RUNFILES_DIR_EXT);
+ PathFragment outputManifestPath = runfilesDir.getRelative("MANIFEST");
+
+ BuildConfiguration config = context.getConfiguration();
+ Artifact outputManifest = context.getAnalysisEnvironment().getDerivedArtifact(
+ outputManifestPath, config.getBinDirectory());
+ context.getAnalysisEnvironment().registerAction(new SymlinkTreeAction(
+ context.getActionOwner(), inputManifest, outputManifest, /*filesetTree=*/false));
+ return outputManifest;
+ }
+
+ /**
+ * Creates an Artifact which writes the "sources only" manifest file.
+ *
+ * @param context the owner for the manifest action
+ * @param runfiles the runfiles
+ * @return the Artifact representing the file write action.
+ */
+ private Artifact createSourceManifest(ActionConstructionContext context, Runfiles runfiles) {
+ // Put the sources only manifest next to the MANIFEST file but call it SOURCES.
+ PathFragment runfilesDir = getRunfilesDirectoryExecPath();
+ if (runfilesDir != null) {
+ PathFragment sourcesManifestPath = runfilesDir.getRelative("SOURCES");
+ Artifact sourceOnlyManifest = context.getAnalysisEnvironment().getDerivedArtifact(
+ sourcesManifestPath, context.getConfiguration().getBinDirectory());
+ context.getAnalysisEnvironment().registerAction(
+ SourceManifestAction.forRunfiles(
+ ManifestType.SOURCES_ONLY, context.getActionOwner(), sourceOnlyManifest, runfiles));
+ return sourceOnlyManifest;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Helper method that returns a collection of artifacts that are necessary for the runfiles of the
+ * given target. Note that the runfile symlink tree is never built, so this may include artifacts
+ * that end up not being used (see {@link Runfiles}).
+ *
+ * @return the Runfiles object
+ */
+
+ private static Runfiles getRunfiles(TransitiveInfoCollection target) {
+ RunfilesProvider runfilesProvider = target.getProvider(RunfilesProvider.class);
+ if (runfilesProvider != null) {
+ return runfilesProvider.getDefaultRunfiles();
+ } else {
+ return Runfiles.EMPTY;
+ }
+ }
+
+ /**
+ * Returns the unmodifiable list of expanded and tokenized 'args' attribute
+ * values.
+ */
+ public List<String> getArgs() {
+ return args;
+ }
+
+ /**
+ * Creates and returns a RunfilesSupport object for the given rule and executable. Note that this
+ * method calls back into the passed in rule to obtain the runfiles.
+ */
+ public static RunfilesSupport withExecutable(RuleContext ruleContext, Runfiles runfiles,
+ Artifact executable) {
+ return new RunfilesSupport(ruleContext, executable, runfiles, ImmutableList.<String>of(),
+ ruleContext.shouldCreateRunfilesSymlinks());
+ }
+
+ /**
+ * Creates and returns a RunfilesSupport object for the given rule and executable. Note that this
+ * method calls back into the passed in rule to obtain the runfiles.
+ */
+ public static RunfilesSupport withExecutable(RuleContext ruleContext, Runfiles runfiles,
+ Artifact executable, boolean createSymlinks) {
+ return new RunfilesSupport(ruleContext, executable, runfiles, ImmutableList.<String>of(),
+ createSymlinks);
+ }
+
+ /**
+ * Creates and returns a RunfilesSupport object for the given rule, executable, runfiles and args.
+ */
+ public static RunfilesSupport withExecutable(RuleContext ruleContext, Runfiles runfiles,
+ Artifact executable, List<String> appendingArgs) {
+ return new RunfilesSupport(ruleContext, executable, runfiles,
+ ImmutableList.copyOf(appendingArgs), ruleContext.shouldCreateRunfilesSymlinks());
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/SourceManifestAction.java b/src/main/java/com/google/devtools/build/lib/analysis/SourceManifestAction.java
new file mode 100644
index 0000000..5aa3bdc
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/SourceManifestAction.java
@@ -0,0 +1,404 @@
+// 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 static java.nio.charset.StandardCharsets.ISO_8859_1;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.Executor.ActionContext;
+import com.google.devtools.build.lib.analysis.actions.AbstractFileWriteAction;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/**
+ * Action to create a manifest of input files for processing by a subsequent
+ * build step (e.g. runfiles symlinking or archive building).
+ *
+ * <p>The manifest's format is specifiable by {@link ManifestType}, in
+ * accordance with the needs of the calling functionality.
+ *
+ * <p>Note that this action carefully avoids building the manifest content in
+ * memory.
+ */
+public class SourceManifestAction extends AbstractFileWriteAction {
+ /**
+ * Action context that tells what workspace suffix we should use.
+ */
+ public interface Context extends ActionContext {
+ PathFragment getRunfilesPrefix();
+ }
+
+ private static final String GUID = "07459553-a3d0-4d37-9d78-18ed942470f4";
+
+ /**
+ * Interface for defining manifest formatting and reporting specifics.
+ */
+ @VisibleForTesting
+ interface ManifestWriter {
+
+ /**
+ * Writes a single line of manifest output.
+ *
+ * @param manifestWriter the output stream
+ * @param rootRelativePath path of an entry relative to the manifest's root
+ * @param symlink (optional) symlink that resolves the above path
+ */
+ void writeEntry(Writer manifestWriter, PathFragment rootRelativePath,
+ @Nullable Artifact symlink) throws IOException;
+
+ /**
+ * Fulfills {@link #ActionMetadata.getMnemonic()}
+ */
+ String getMnemonic();
+
+ /**
+ * Fulfills {@link #AbstractAction.getRawProgressMessage()}
+ */
+ String getRawProgressMessage();
+ }
+
+ /**
+ * The strategy we use to write manifest entries.
+ */
+ private final ManifestWriter manifestWriter;
+
+ /**
+ * The runfiles for which to create the symlink tree.
+ */
+ private final Runfiles runfiles;
+
+ /**
+ * If non-null, the paths should be computed relative to this path fragment.
+ */
+ private final PathFragment root;
+
+ /**
+ * Creates a new AbstractSourceManifestAction instance using latin1 encoding
+ * to write the manifest file and with a specified root path for manifest entries.
+ *
+ * @param manifestWriter the strategy to use to write manifest entries
+ * @param owner the action owner
+ * @param output the file to which to write the manifest
+ * @param runfiles runfiles
+ * @param root the artifacts' root-relative path is relativized to this before writing it out
+ */
+ private SourceManifestAction(ManifestWriter manifestWriter, ActionOwner owner, Artifact output,
+ Runfiles runfiles, PathFragment root) {
+ super(owner, getDependencies(runfiles), output, false);
+ this.manifestWriter = manifestWriter;
+ this.runfiles = runfiles;
+ this.root = root;
+ }
+
+ @VisibleForTesting
+ public void writeOutputFile(OutputStream out, EventHandler eventHandler, String workspaceSuffix)
+ throws IOException {
+ writeFile(out, runfiles.getRunfilesInputs(
+ root, workspaceSuffix, eventHandler, getOwner().getLocation()));
+ }
+
+ @Override
+ public DeterministicWriter newDeterministicWriter(EventHandler eventHandler, Executor executor)
+ throws IOException {
+ final Pair<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> runfilesInputs =
+ runfiles.getRunfilesInputs(root,
+ executor.getContext(Context.class).getRunfilesPrefix().toString(), eventHandler,
+ getOwner().getLocation());
+ return new DeterministicWriter() {
+ @Override
+ public void writeOutputFile(OutputStream out) throws IOException {
+ writeFile(out, runfilesInputs);
+ }
+ };
+ }
+
+ /**
+ * Returns the input dependencies for this action. Note we don't need to create the symlink
+ * target Artifacts before we write the output manifest, so this Action does not have to
+ * depend on them. The only necessary dependencies are pruning manifests, which must be read
+ * to properly prune the tree.
+ */
+ private static Collection<Artifact> getDependencies(Runfiles runfiles) {
+ ImmutableList.Builder<Artifact> builder = ImmutableList.builder();
+ for (Runfiles.PruningManifest manifest : runfiles.getPruningManifests()) {
+ builder.add(manifest.getManifestFile());
+ }
+ return builder.build();
+ }
+
+ /**
+ * Sort the entries in both the normal and root manifests and write the output
+ * file.
+ *
+ * @param out is the message stream to write errors to.
+ * @param output The actual mapping of the output manifest.
+ * @throws IOException
+ */
+ private void writeFile(OutputStream out,
+ Pair<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> output)
+ throws IOException {
+ Writer manifestFile = new BufferedWriter(new OutputStreamWriter(out, ISO_8859_1));
+
+ Comparator<Map.Entry<PathFragment, Artifact>> fragmentComparator =
+ new Comparator<Map.Entry<PathFragment, Artifact>>() {
+ @Override
+ public int compare(Map.Entry<PathFragment, Artifact> path1,
+ Map.Entry<PathFragment, Artifact> path2) {
+ return path1.getKey().compareTo(path2.getKey());
+ }
+ };
+
+ List<Map.Entry<PathFragment, Artifact>> sortedRootLinks =
+ new ArrayList<>(output.second.entrySet());
+ Collections.sort(sortedRootLinks, fragmentComparator);
+
+ List<Map.Entry<PathFragment, Artifact>> sortedManifest =
+ new ArrayList<>(output.first.entrySet());
+ Collections.sort(sortedManifest, fragmentComparator);
+
+ for (Map.Entry<PathFragment, Artifact> line : sortedRootLinks) {
+ manifestWriter.writeEntry(manifestFile, line.getKey(), line.getValue());
+ }
+
+ for (Map.Entry<PathFragment, Artifact> line : sortedManifest) {
+ manifestWriter.writeEntry(manifestFile, line.getKey(), line.getValue());
+ }
+ manifestFile.flush();
+ }
+
+ @Override
+ public String getMnemonic() {
+ return manifestWriter.getMnemonic();
+ }
+
+ @Override
+ protected String getRawProgressMessage() {
+ return manifestWriter.getRawProgressMessage() + " for " + getOwner().getLabel();
+ }
+
+ @Override
+ protected String computeKey() {
+ Fingerprint f = new Fingerprint();
+ f.addString(GUID);
+ Map<PathFragment, Artifact> symlinks = runfiles.getSymlinksAsMap();
+ f.addInt(symlinks.size());
+ for (Map.Entry<PathFragment, Artifact> symlink : symlinks.entrySet()) {
+ f.addPath(symlink.getKey());
+ f.addPath(symlink.getValue().getPath());
+ }
+ Map<PathFragment, Artifact> rootSymlinks = runfiles.getRootSymlinksAsMap();
+ f.addInt(rootSymlinks.size());
+ for (Map.Entry<PathFragment, Artifact> rootSymlink : rootSymlinks.entrySet()) {
+ f.addPath(rootSymlink.getKey());
+ f.addPath(rootSymlink.getValue().getPath());
+ }
+
+ if (root != null) {
+ for (Artifact artifact : runfiles.getArtifactsWithoutMiddlemen()) {
+ f.addPath(artifact.getRootRelativePath().relativeTo(root));
+ f.addPath(artifact.getPath());
+ }
+ } else {
+ for (Artifact artifact : runfiles.getArtifactsWithoutMiddlemen()) {
+ f.addPath(artifact.getRootRelativePath());
+ f.addPath(artifact.getPath());
+ }
+ }
+ return f.hexDigestAndReset();
+ }
+
+ /**
+ * Supported manifest writing strategies.
+ */
+ public static enum ManifestType implements ManifestWriter {
+
+ /**
+ * Writes each line as:
+ *
+ * [rootRelativePath] [resolvingSymlink]
+ *
+ * <p>This strategy is suitable for creating an input manifest to a source view tree. Its
+ * output is a valid input to {@link com.google.devtools.build.lib.analysis.SymlinkTreeAction}.
+ */
+ SOURCE_SYMLINKS {
+ @Override
+ public void writeEntry(Writer manifestWriter, PathFragment rootRelativePath, Artifact symlink)
+ throws IOException {
+ manifestWriter.append(rootRelativePath.getPathString());
+ // This trailing whitespace is REQUIRED to process the single entry line correctly.
+ manifestWriter.append(' ');
+ if (symlink != null) {
+ manifestWriter.append(symlink.getPath().getPathString());
+ }
+ manifestWriter.append('\n');
+ }
+
+ @Override
+ public String getMnemonic() {
+ return "SourceSymlinkManifest";
+ }
+
+ @Override
+ public String getRawProgressMessage() {
+ return "Creating source manifest";
+ }
+ },
+
+ /**
+ * Writes each line as:
+ *
+ * [rootRelativePath]
+ *
+ * <p>This strategy is suitable for an input into a packaging system (notably .par) that
+ * consumes a list of all source files but needs that list to be constant with respect to
+ * how the user has their client laid out on local disk.
+ */
+ SOURCES_ONLY {
+ @Override
+ public void writeEntry(Writer manifestWriter, PathFragment rootRelativePath, Artifact symlink)
+ throws IOException {
+ manifestWriter.append(rootRelativePath.getPathString());
+ manifestWriter.append('\n');
+ manifestWriter.flush();
+ }
+
+ @Override
+ public String getMnemonic() {
+ return "PackagingSourcesManifest";
+ }
+
+ @Override
+ public String getRawProgressMessage() {
+ return "Creating file sources list";
+ }
+ }
+ }
+
+ /** Creates an action for the given runfiles. */
+ public static SourceManifestAction forRunfiles(ManifestType manifestType, ActionOwner owner,
+ Artifact output, Runfiles runfiles) {
+ return new SourceManifestAction(manifestType, owner, output, runfiles, null);
+ }
+
+ /**
+ * Builder class to construct {@link SourceManifestAction} instances.
+ */
+ public static final class Builder {
+ private final ManifestWriter manifestWriter;
+ private final ActionOwner owner;
+ private final Artifact output;
+ private PathFragment top;
+ private final Runfiles.Builder runfilesBuilder = new Runfiles.Builder();
+
+ public Builder(ManifestType manifestType, ActionOwner owner, Artifact output) {
+ manifestWriter = manifestType;
+ this.owner = owner;
+ this.output = output;
+ }
+
+ @VisibleForTesting
+ Builder(ManifestWriter manifestWriter, ActionOwner owner, Artifact output) {
+ this.manifestWriter = manifestWriter;
+ this.owner = owner;
+ this.output = output;
+ }
+
+ public SourceManifestAction build() {
+ return new SourceManifestAction(manifestWriter, owner, output, runfilesBuilder.build(), top);
+ }
+
+ /**
+ * Sets the path fragment which is used to relativize the artifacts' root
+ * relative paths further. Most likely, you don't need this.
+ */
+ public Builder setTopLevel(PathFragment top) {
+ this.top = top;
+ return this;
+ }
+
+ /**
+ * Adds a set of symlinks from the artifacts' root-relative paths to the
+ * artifacts themselves.
+ */
+ public Builder addSymlinks(Iterable<Artifact> artifacts) {
+ runfilesBuilder.addArtifacts(artifacts);
+ return this;
+ }
+
+ /**
+ * Adds a map of symlinks.
+ */
+ public Builder addSymlinks(Map<PathFragment, Artifact> symlinks) {
+ runfilesBuilder.addSymlinks(symlinks);
+ return this;
+ }
+
+ /**
+ * Adds a single symlink.
+ */
+ public Builder addSymlink(PathFragment link, Artifact target) {
+ runfilesBuilder.addSymlink(link, target);
+ return this;
+ }
+
+ /**
+ * <p>Adds a mapping of Artifacts to the directory above the normal symlink
+ * forest base.
+ */
+ public Builder addRootSymlinks(Map<PathFragment, Artifact> rootSymlinks) {
+ runfilesBuilder.addRootSymlinks(rootSymlinks);
+ return this;
+ }
+
+ /**
+ * Set an expander function for the symlinks.
+ */
+ @VisibleForTesting
+ Builder setSymlinksExpander(
+ Function<Map<PathFragment, Artifact>, Map<PathFragment, Artifact>> expander) {
+ runfilesBuilder.setManifestExpander(expander);
+ return this;
+ }
+
+ /**
+ * Adds a runfiles pruning manifest.
+ */
+ @VisibleForTesting
+ Builder addPruningManifest(Runfiles.PruningManifest manifest) {
+ runfilesBuilder.addPruningManifest(manifest);
+ return this;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeAction.java b/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeAction.java
new file mode 100644
index 0000000..2dc0d4a
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeAction.java
@@ -0,0 +1,109 @@
+// 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.ImmutableList;
+import com.google.devtools.build.lib.actions.AbstractAction;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.util.Fingerprint;
+
+/**
+ * Action responsible for the symlink tree creation.
+ * Used to generate runfiles and fileset symlink farms.
+ */
+public class SymlinkTreeAction extends AbstractAction {
+
+ private static final String GUID = "63412bda-4026-4c8e-a3ad-7deb397728d4";
+
+ private final Artifact inputManifest;
+ private final Artifact outputManifest;
+ private final boolean filesetTree;
+
+ /**
+ * Creates SymlinkTreeAction instance.
+ *
+ * @param owner action owner
+ * @param inputManifest exec path to the input runfiles manifest
+ * @param outputManifest exec path to the generated symlink tree manifest
+ * (must have "MANIFEST" base name). Symlink tree root
+ * will be set to the artifact's parent directory.
+ * @param filesetTree true if this is fileset symlink tree,
+ * false if this is a runfiles symlink tree.
+ */
+ public SymlinkTreeAction(ActionOwner owner, Artifact inputManifest, Artifact outputManifest,
+ boolean filesetTree) {
+ super(owner, ImmutableList.of(inputManifest), ImmutableList.of(outputManifest));
+ Preconditions.checkArgument(outputManifest.getPath().getBaseName().equals("MANIFEST"));
+ this.inputManifest = inputManifest;
+ this.outputManifest = outputManifest;
+ this.filesetTree = filesetTree;
+ }
+
+ public Artifact getInputManifest() {
+ return inputManifest;
+ }
+
+ public Artifact getOutputManifest() {
+ return outputManifest;
+ }
+
+ public boolean isFilesetTree() {
+ return filesetTree;
+ }
+
+ @Override
+ public String getMnemonic() {
+ return "SymlinkTree";
+ }
+
+ @Override
+ protected String getRawProgressMessage() {
+ return (filesetTree ? "Creating Fileset tree " : "Creating runfiles tree ")
+ + outputManifest.getExecPath().getParentDirectory().getPathString();
+ }
+
+ @Override
+ protected String computeKey() {
+ Fingerprint f = new Fingerprint();
+ f.addString(GUID);
+ f.addInt(filesetTree ? 1 : 0);
+ return f.hexDigestAndReset();
+ }
+
+ @Override
+ public ResourceSet estimateResourceConsumption(Executor executor) {
+ // Return null here to indicate that resources would be managed manually
+ // during action execution.
+ return null;
+ }
+
+ @Override
+ public String describeStrategy(Executor executor) {
+ return "local"; // Symlink tree is always generated locally.
+ }
+
+ @Override
+ public void execute(
+ ActionExecutionContext actionExecutionContext)
+ throws ActionExecutionException, InterruptedException {
+ actionExecutionContext.getExecutor().getContext(SymlinkTreeActionContext.class)
+ .createSymlinks(this, actionExecutionContext);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeActionContext.java b/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeActionContext.java
new file mode 100644
index 0000000..fe61056
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeActionContext.java
@@ -0,0 +1,31 @@
+// 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.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.Executor.ActionContext;
+
+/**
+ * Action context for symlink tree actions (an action that creates a tree of symlinks).
+ */
+public interface SymlinkTreeActionContext extends ActionContext {
+
+ /**
+ * Creates the symlink tree.
+ */
+ void createSymlinks(SymlinkTreeAction action,
+ ActionExecutionContext actionExecutionContext)
+ throws ActionExecutionException, InterruptedException;
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TargetAndConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/TargetAndConfiguration.java
new file mode 100644
index 0000000..cc0feb6
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TargetAndConfiguration.java
@@ -0,0 +1,104 @@
+// 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.Function;
+import com.google.common.base.Preconditions;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
+import com.google.devtools.build.lib.syntax.Label;
+
+import java.util.Objects;
+
+import javax.annotation.Nullable;
+
+/**
+ * Refers to the pair of a target and a configuration and certain additional information. Not the
+ * same as {@link ConfiguredTarget} -- that also contains the result of the analysis phase.
+ */
+public class TargetAndConfiguration {
+ private final Target target;
+ @Nullable private final BuildConfiguration configuration;
+
+ public TargetAndConfiguration(Target target, @Nullable BuildConfiguration configuration) {
+ this.target = Preconditions.checkNotNull(target);
+ this.configuration = configuration;
+ }
+
+ public TargetAndConfiguration(ConfiguredTarget configuredTarget) {
+ this.target = Preconditions.checkNotNull(configuredTarget).getTarget();
+ this.configuration = configuredTarget.getConfiguration();
+ }
+
+ // The node name in the graph. The name should be unique.
+ // It is not suitable for user display.
+ public String getName() {
+ return target.getLabel() + " "
+ + (configuration == null ? "null" : configuration.shortCacheKey());
+ }
+
+ public static final Function<TargetAndConfiguration, String> NAME_FUNCTION =
+ new Function<TargetAndConfiguration, String>() {
+ @Override
+ public String apply(TargetAndConfiguration node) {
+ return node.getName();
+ }
+ };
+
+ public static final Function<TargetAndConfiguration, ConfiguredTargetKey>
+ TO_LABEL_AND_CONFIGURATION = new Function<TargetAndConfiguration, ConfiguredTargetKey>() {
+ @Override
+ public ConfiguredTargetKey apply(TargetAndConfiguration input) {
+ return new ConfiguredTargetKey(input.getLabel(), input.getConfiguration());
+ }
+ };
+
+ @Override
+ public boolean equals(Object that) {
+ if (this == that) {
+ return true;
+ }
+ if (!(that instanceof TargetAndConfiguration)) {
+ return false;
+ }
+
+ TargetAndConfiguration thatNode = (TargetAndConfiguration) that;
+ return thatNode.target.getLabel().equals(this.target.getLabel()) &&
+ thatNode.configuration == this.configuration;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(target.getLabel(), configuration);
+ }
+
+ @Override
+ public String toString() {
+ return target.getLabel() + " (" + configuration + ")";
+ }
+
+ public Target getTarget() {
+ return target;
+ }
+
+ public Label getLabel() {
+ return target.getLabel();
+ }
+
+ @Nullable
+ public BuildConfiguration getConfiguration() {
+ return configuration;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java
new file mode 100644
index 0000000..fcfec55
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TargetCompleteEvent.java
@@ -0,0 +1,75 @@
+// 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.Iterables;
+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.syntax.Label;
+import com.google.devtools.build.skyframe.SkyValue;
+
+/**
+ * This event is fired as soon as a target is either built or fails.
+ */
+public final class TargetCompleteEvent implements SkyValue {
+
+ private final ConfiguredTarget target;
+ private final NestedSet<Label> rootCauses;
+
+ private TargetCompleteEvent(ConfiguredTarget target, NestedSet<Label> rootCauses) {
+ this.target = target;
+ this.rootCauses = (rootCauses == null)
+ ? NestedSetBuilder.<Label>emptySet(Order.STABLE_ORDER)
+ : rootCauses;
+ }
+
+ /**
+ * Construct a successful target completion event.
+ */
+ public static TargetCompleteEvent createSuccessful(ConfiguredTarget ct) {
+ return new TargetCompleteEvent(ct, null);
+ }
+
+ /**
+ * Construct a target completion event for a failed target, with the given non-empty root causes.
+ */
+ public static TargetCompleteEvent createFailed(ConfiguredTarget ct, NestedSet<Label> rootCauses) {
+ Preconditions.checkArgument(!Iterables.isEmpty(rootCauses));
+ return new TargetCompleteEvent(ct, rootCauses);
+ }
+
+ /**
+ * Returns the target associated with the event.
+ */
+ public ConfiguredTarget getTarget() {
+ return target;
+ }
+
+ /**
+ * Determines whether the target has failed or succeeded.
+ */
+ public boolean failed() {
+ return !rootCauses.isEmpty();
+ }
+
+ /**
+ * Get the root causes of the target. May be empty.
+ */
+ public Iterable<Label> getRootCauses() {
+ return rootCauses;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TargetContext.java b/src/main/java/com/google/devtools/build/lib/analysis/TargetContext.java
new file mode 100644
index 0000000..9c95db3
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TargetContext.java
@@ -0,0 +1,96 @@
+// 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.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.packages.PackageSpecification;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.syntax.Label;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+/**
+ * A helper class for building {@link ConfiguredTarget} instances, in particular for non-rule ones.
+ * For {@link RuleConfiguredTarget} instances, use {@link RuleContext} instead,
+ * which is a subclass of this class.
+ *
+ * <p>The class is intended to be sub-classed by RuleContext, in order to share the code. However,
+ * it's not intended for sub-classing beyond that, and the constructor is intentionally package
+ * private to enforce that.
+ */
+public class TargetContext {
+
+ private final AnalysisEnvironment env;
+ private final Target target;
+ private final BuildConfiguration configuration;
+ /**
+ * This list only contains prerequisites that are not declared in rule attributes, with the
+ * exception of visibility (i.e., visibility is represented here, even though it is a rule
+ * attribute in case of a rule). Rule attributes are handled by the {@link RuleContext} subclass.
+ */
+ private final List<ConfiguredTarget> directPrerequisites;
+ private final NestedSet<PackageSpecification> visibility;
+
+ /**
+ * The constructor is intentionally package private.
+ */
+ TargetContext(AnalysisEnvironment env, Target target, BuildConfiguration configuration,
+ List<ConfiguredTarget> directPrerequisites,
+ NestedSet<PackageSpecification> visibility) {
+ this.env = env;
+ this.target = target;
+ this.configuration = configuration;
+ this.directPrerequisites = directPrerequisites;
+ this.visibility = visibility;
+ }
+
+ public AnalysisEnvironment getAnalysisEnvironment() {
+ return env;
+ }
+
+ public Target getTarget() {
+ return target;
+ }
+
+ public Label getLabel() {
+ return target.getLabel();
+ }
+
+ /**
+ * Returns the configuration for this target. This may return null if the target is supposed to be
+ * configuration-independent (like an input file, or a visibility rule). However, this is
+ * guaranteed to be non-null for rules and for output files.
+ */
+ @Nullable
+ public BuildConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ public NestedSet<PackageSpecification> getVisibility() {
+ return visibility;
+ }
+
+ TransitiveInfoCollection findDirectPrerequisite(Label label, BuildConfiguration config) {
+ for (ConfiguredTarget prerequisite : directPrerequisites) {
+ if (prerequisite.getLabel().equals(label) && (prerequisite.getConfiguration() == config)) {
+ return prerequisite;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TempsProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/TempsProvider.java
new file mode 100644
index 0000000..109992e
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TempsProvider.java
@@ -0,0 +1,42 @@
+// 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.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+import java.util.Collection;
+
+/**
+ * A {@link TransitiveInfoProvider} for rule classes that save extra files when
+ * {@code --save_temps} is in effect.
+ */
+@Immutable
+public final class TempsProvider implements TransitiveInfoProvider {
+
+ private final ImmutableList<Artifact> temps;
+
+ public TempsProvider(ImmutableList<Artifact> temps) {
+ this.temps = temps;
+ }
+
+ /**
+ * Return the extra artifacts to save when {@code --save_temps} is in effect.
+ */
+ public Collection<Artifact> getTemps() {
+ return temps;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactContext.java b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactContext.java
new file mode 100644
index 0000000..c9b8e51
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactContext.java
@@ -0,0 +1,104 @@
+// 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.collect.ImmutableSet;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Contains options which control the set of artifacts to build for top-level targets.
+ */
+@Immutable
+public final class TopLevelArtifactContext {
+
+ public static final TopLevelArtifactContext DEFAULT = new TopLevelArtifactContext(
+ "", /*compileOnly=*/false, /*compilationPrerequisitesOnly*/false,
+ /*runTestsExclusively=*/false, /*outputGroups=*/ImmutableSet.<String>of(),
+ /*shouldRunTests=*/false);
+
+ private final String buildCommand;
+ private final boolean compileOnly;
+ private final boolean compilationPrerequisitesOnly;
+ private final boolean runTestsExclusively;
+ private final ImmutableSet<String> outputGroups;
+ private final boolean shouldRunTests;
+
+ public TopLevelArtifactContext(String buildCommand, boolean compileOnly,
+ boolean compilationPrerequisitesOnly, boolean runTestsExclusively,
+ ImmutableSet<String> outputGroups, boolean shouldRunTests) {
+ this.buildCommand = buildCommand;
+ this.compileOnly = compileOnly;
+ this.compilationPrerequisitesOnly = compilationPrerequisitesOnly;
+ this.runTestsExclusively = runTestsExclusively;
+ this.outputGroups = outputGroups;
+ this.shouldRunTests = shouldRunTests;
+ }
+
+ /** Returns the build command as a string. */
+ public String buildCommand() {
+ return buildCommand;
+ }
+
+ /** Returns the value of the --compile_only flag. */
+ public boolean compileOnly() {
+ return compileOnly;
+ }
+
+ /** Returns the value of the --compilation_prerequisites_only flag. */
+ public boolean compilationPrerequisitesOnly() {
+ return compilationPrerequisitesOnly;
+ }
+
+ /** Whether to run tests in exclusive mode. */
+ public boolean runTestsExclusively() {
+ return runTestsExclusively;
+ }
+
+ /** Returns the value of the --output_groups flag. */
+ public Set<String> outputGroups() {
+ return outputGroups;
+ }
+
+ /** Whether the top-level request command may run tests. */
+ public boolean shouldRunTests() {
+ return shouldRunTests;
+ }
+
+ // TopLevelArtifactContexts are stored in maps in BuildView,
+ // so equals() and hashCode() need to work.
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof TopLevelArtifactContext) {
+ TopLevelArtifactContext otherContext = (TopLevelArtifactContext) other;
+ return buildCommand.equals(otherContext.buildCommand)
+ && compileOnly == otherContext.compileOnly
+ && compilationPrerequisitesOnly == otherContext.compilationPrerequisitesOnly
+ && runTestsExclusively == otherContext.runTestsExclusively
+ && outputGroups.equals(otherContext.outputGroups)
+ && shouldRunTests == otherContext.shouldRunTests;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(buildCommand, compileOnly, compilationPrerequisitesOnly,
+ runTestsExclusively, outputGroups, shouldRunTests);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java
new file mode 100644
index 0000000..3c025f4
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java
@@ -0,0 +1,158 @@
+// 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.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.rules.test.TestProvider;
+
+/**
+ * A small static class containing utility methods for handling the inclusion of
+ * extra top-level artifacts into the build.
+ */
+public final class TopLevelArtifactHelper {
+
+ private TopLevelArtifactHelper() {
+ // Prevent instantiation.
+ }
+
+ /** Returns command-specific artifacts which may exist for a given target and build command. */
+ public static final Iterable<Artifact> getCommandArtifacts(TransitiveInfoCollection target,
+ String buildCommand) {
+ TopLevelArtifactProvider provider = target.getProvider(TopLevelArtifactProvider.class);
+ if (provider != null
+ && provider.getCommandsForExtraArtifacts().contains(buildCommand.toLowerCase())) {
+ return provider.getArtifactsForCommand();
+ } else {
+ return ImmutableList.of();
+ }
+ }
+
+ /**
+ * Utility function to form a list of all test output Artifacts of the given targets to test.
+ */
+ public static ImmutableCollection<Artifact> getAllArtifactsToTest(
+ Iterable<? extends TransitiveInfoCollection> targets) {
+ if (targets == null) {
+ return ImmutableList.of();
+ }
+ ImmutableList.Builder<Artifact> allTestArtifacts = ImmutableList.builder();
+ for (TransitiveInfoCollection target : targets) {
+ allTestArtifacts.addAll(TestProvider.getTestStatusArtifacts(target));
+ }
+ return allTestArtifacts.build();
+ }
+
+ /**
+ * Utility function to form a NestedSet of all top-level Artifacts of the given targets.
+ */
+ public static NestedSet<Artifact> getAllArtifactsToBuild(
+ Iterable<? extends TransitiveInfoCollection> targets, TopLevelArtifactContext options) {
+ NestedSetBuilder<Artifact> allArtifacts = NestedSetBuilder.stableOrder();
+ for (TransitiveInfoCollection target : targets) {
+ allArtifacts.addTransitive(getAllArtifactsToBuild(target, options));
+ }
+ return allArtifacts.build();
+ }
+
+ /**
+ * Returns all artifacts to build if this target is requested as a top-level target. The resulting
+ * set includes the temps and either the files to compile, if
+ * {@code options.compileOnly() == true}, or the files to run.
+ *
+ * <p>Calls to this method should generally return quickly; however, the runfiles computation can
+ * be lazy, in which case it can be expensive on the first call. Subsequent calls may or may not
+ * return the same {@code Iterable} instance.
+ */
+ public static NestedSet<Artifact> getAllArtifactsToBuild(TransitiveInfoCollection target,
+ TopLevelArtifactContext options) {
+ NestedSetBuilder<Artifact> allArtifacts = NestedSetBuilder.stableOrder();
+ TempsProvider tempsProvider = target.getProvider(TempsProvider.class);
+ if (tempsProvider != null) {
+ allArtifacts.addAll(tempsProvider.getTemps());
+ }
+
+ TopLevelArtifactProvider topLevelArtifactProvider =
+ target.getProvider(TopLevelArtifactProvider.class);
+ if (topLevelArtifactProvider != null) {
+ for (String outputGroup : options.outputGroups()) {
+ NestedSet<Artifact> results = topLevelArtifactProvider.getOutputGroup(outputGroup);
+ if (results != null) {
+ allArtifacts.addTransitive(results);
+ }
+ }
+ }
+
+ if (options.compileOnly()) {
+ FilesToCompileProvider provider = target.getProvider(FilesToCompileProvider.class);
+ if (provider != null) {
+ allArtifacts.addAll(provider.getFilesToCompile());
+ }
+ } else if (options.compilationPrerequisitesOnly()) {
+ CompilationPrerequisitesProvider provider =
+ target.getProvider(CompilationPrerequisitesProvider.class);
+ if (provider != null) {
+ allArtifacts.addTransitive(provider.getCompilationPrerequisites());
+ }
+ } else {
+ FilesToRunProvider filesToRunProvider = target.getProvider(FilesToRunProvider.class);
+ boolean hasRunfilesSupport = false;
+ if (filesToRunProvider != null) {
+ allArtifacts.addAll(filesToRunProvider.getFilesToRun());
+ hasRunfilesSupport = filesToRunProvider.getRunfilesSupport() != null;
+ }
+
+ if (!hasRunfilesSupport) {
+ RunfilesProvider runfilesProvider =
+ target.getProvider(RunfilesProvider.class);
+ if (runfilesProvider != null) {
+ allArtifacts.addTransitive(runfilesProvider.getDefaultRunfiles().getAllArtifacts());
+ }
+ }
+
+ AlwaysBuiltArtifactsProvider forcedArtifacts = target.getProvider(
+ AlwaysBuiltArtifactsProvider.class);
+ if (forcedArtifacts != null) {
+ allArtifacts.addTransitive(forcedArtifacts.getArtifactsToAlwaysBuild());
+ }
+ }
+
+ allArtifacts.addAll(getCommandArtifacts(target, options.buildCommand()));
+ allArtifacts.addAll(getCoverageArtifacts(target, options));
+ return allArtifacts.build();
+ }
+
+ private static Iterable<Artifact> getCoverageArtifacts(TransitiveInfoCollection target,
+ TopLevelArtifactContext topLevelOptions) {
+ if (!topLevelOptions.compileOnly() && !topLevelOptions.compilationPrerequisitesOnly()
+ && topLevelOptions.shouldRunTests()) {
+ // Add baseline code coverage artifacts if we are collecting code coverage. We do that only
+ // when running tests.
+ // It might be slightly faster to first check if any configuration has coverage enabled.
+ if (target.getConfiguration() != null
+ && target.getConfiguration().isCodeCoverageEnabled()) {
+ BaselineCoverageArtifactsProvider provider =
+ target.getProvider(BaselineCoverageArtifactsProvider.class);
+ if (provider != null) {
+ return provider.getBaselineCoverageArtifacts();
+ }
+ }
+ }
+ return ImmutableList.of();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactProvider.java
new file mode 100644
index 0000000..e2a2d57
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactProvider.java
@@ -0,0 +1,61 @@
+// 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.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+
+/**
+ * ConfiguredTargets implementing this interface can provide command-specific
+ * and unconditional extra artifacts to the build.
+ */
+@Immutable
+public final class TopLevelArtifactProvider implements TransitiveInfoProvider {
+
+ private final ImmutableList<String> commandsForExtraArtifacts;
+ private final ImmutableList<Artifact> artifactsForCommand;
+ private final ImmutableMap<String, NestedSet<Artifact>> outputGroups;
+
+ public TopLevelArtifactProvider(ImmutableList<String> commandsForExtraArtifacts,
+ ImmutableList<Artifact> artifactsForCommand) {
+ this.commandsForExtraArtifacts = commandsForExtraArtifacts;
+ this.artifactsForCommand = artifactsForCommand;
+ this.outputGroups = ImmutableMap.<String, NestedSet<Artifact>>of();
+ }
+
+ public TopLevelArtifactProvider(String key, NestedSet<Artifact> artifactsToBuild) {
+ this.commandsForExtraArtifacts = ImmutableList.of();
+ this.artifactsForCommand = ImmutableList.of();
+ this.outputGroups = ImmutableMap.<String, NestedSet<Artifact>>of(key, artifactsToBuild);
+ }
+
+ /** Returns the commands (in lowercase) that this provider should provide artifacts for. */
+ public ImmutableList<String> getCommandsForExtraArtifacts() {
+ return commandsForExtraArtifacts;
+ }
+
+ /** Returns the extra artifacts for the commands. */
+ public ImmutableList<Artifact> getArtifactsForCommand() {
+ return artifactsForCommand;
+ }
+
+ /** Returns artifacts that are to be built for every command. */
+ public NestedSet<Artifact> getOutputGroup(String outputGroupName) {
+ return outputGroups.get(outputGroupName);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoCollection.java b/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoCollection.java
new file mode 100644
index 0000000..82396ee
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoCollection.java
@@ -0,0 +1,118 @@
+// 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.collect.UnmodifiableIterator;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.SkylarkModule;
+
+import javax.annotation.Nullable;
+
+/**
+ * Objects that implement this interface bundle multiple {@link TransitiveInfoProvider} interfaces.
+ *
+ * <p>This interface (together with {@link TransitiveInfoProvider} is the cornerstone of the data
+ * model of the analysis phase.
+ *
+ * <p>The computation a configured target does is allowed to depend on the following things:
+ * <ul>
+ * <li>The associated Target (which will usually be a Rule)
+ * <li>Its own configuration (the configured target does not have access to other configurations,
+ * e.g. the host configuration, though)
+ * <li>The transitive info providers and labels of its direct dependencies.
+ * </ul>
+ *
+ * <p>And these are the only inputs. Notably, a configured target is not supposed to access
+ * other configured targets, the transitive info collections of configured targets it does not
+ * directly depend on, the actions created by anyone else or the contents of any input file. We
+ * strive to make it impossible for configured targets to do these things.
+ *
+ * <p>A configured target is expected to produce the following data during its analysis:
+ * <ul>
+ * <li>A number of Artifacts and Actions generating them
+ * <li>A set of {@link TransitiveInfoProvider}s that it passes on to the targets directly dependent
+ * on it
+ * </ul>
+ *
+ * <p>The information that can be passed on to dependent targets by way of
+ * {@link TransitiveInfoProvider} is subject to constraints (which are detailed in the
+ * documentation of that class).
+ *
+ * <p>Configured targets are currently allowed to create artifacts at any exec path. It would be
+ * better if they could be constrained to a subtree based on the label of the configured target,
+ * but this is currently not feasible because multiple rules violate this constraint and the
+ * output format is part of its interface.
+ *
+ * <p>In principle, multiple configured targets should not create actions with conflicting
+ * outputs. There are still a few exceptions to this rule that are slated to be eventually
+ * removed, we have provisions to handle this case (Action instances that share at least one
+ * output file are required to be exactly the same), but this does put some pressure on the design
+ * and we are eventually planning to eliminate this option.
+ *
+ * <p>These restrictions together make it possible to:
+ * <ul>
+ * <li>Correctly cache the analysis phase; by tightly constraining what a configured target is
+ * allowed to access and what it is not, we can know when it needs to invalidate a particular
+ * one and when it can reuse an already existing one.
+ * <li>Serialize / deserialize individual configured targets at will, making it possible for
+ * example to swap out part of the analysis state if there is memory pressure or to move them in
+ * persistent storage so that the state can be reconstructed at a different time or in a
+ * different process. The stretch goal is to eventually facilitate cross-uses caching of this
+ * information.
+ * </ul>
+ *
+ * <p>Implementations of build rules should <b>not</b> hold on to references to the
+ * {@link TransitiveInfoCollection}s representing their direct prerequisites in order to reduce
+ * their memory footprint (otherwise, the referenced object could refer one of its direct
+ * dependencies in turn, thereby making the size of the objects reachable from a single instance
+ * unbounded).
+ *
+ * @see TransitiveInfoProvider
+ */
+@SkylarkModule(name = "target", doc = "A BUILD target.")
+public interface TransitiveInfoCollection extends Iterable<TransitiveInfoProvider> {
+
+ /**
+ * Returns the transitive information provider requested, or null if the provider is not found.
+ * The provider has to be a TransitiveInfoProvider Java class.
+ */
+ @Nullable <P extends TransitiveInfoProvider> P getProvider(Class<P> provider);
+
+ /**
+ * Returns the label associated with this prerequisite.
+ */
+ Label getLabel();
+
+ /**
+ * <p>Returns the {@link BuildConfiguration} for which this transitive info collection is defined.
+ * Configuration is defined for all configured targets with exception of {@link
+ * InputFileConfiguredTarget} and {@link PackageGroupConfiguredTarget} for which it is always
+ * <b>null</b>.</p>
+ */
+ @Nullable BuildConfiguration getConfiguration();
+
+ /**
+ * Returns the transitive information requested or null, if the information is not found.
+ * The transitive information has to have been added using the Skylark framework.
+ */
+ @Nullable Object get(String providerKey);
+
+ /**
+ * Returns an unmodifiable iterator over the transitive info providers in the collections.
+ */
+ @Override
+ UnmodifiableIterator<TransitiveInfoProvider> iterator();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProvider.java
new file mode 100644
index 0000000..37ec191
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProvider.java
@@ -0,0 +1,60 @@
+// 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;
+
+/**
+ * This marker interface must be extended by every interface that represents
+ * rolled-up data about the transitive closure of a configured target.
+ *
+ * TransitiveInfoProviders need to be serializable, and for that reason they must conform to
+ * the following restrictions:
+ *
+ * <ul>
+ * <li>The provider interface must directly extend {@code TransitiveInfoProvider}.
+ * <li>Every method must return immutable data.</li>
+ * <li>Every method must return the same object if called multiple times with the same
+ * arguments.</li>
+ * <li>Overloading a method name multiple times is forbidden.</li>
+ * <li>The return type of a method must satisfy one of the following conditions:
+ * <ul>
+ * <li>It must be from the set of {String, Integer, int, Boolean, bool, Label, PathFragment,
+ * Artifact}, OR</li>
+ * <li>it must be an ImmutableList/List/Collection/Iterable of T, where T is either
+ * one of the types above with a default serializer or T implements ValueSerializer), OR</li>
+ * <li>it must be serializable (TBD)</li>
+ * </ul>
+ * <li>If the method takes arguments, it must declare a custom serializer (TBD).</li>
+ * </ul>
+ *
+ * <p>Some typical uses of this interface are:
+ * <ul>
+ * <li>The set of Python source files in the transitive closure of this rule
+ * <li>The set of declared C++ header files in the transitive closure
+ * <li>The files that need to be built when the target is mentioned on the command line
+ * </ul>
+ *
+ * <p>Note that if implemented naively, this would result in the memory requirements
+ * being O(n^2): in a long dependency chain, if every target adds one single artifact, storing the
+ * transitive closures of every rule would take 1+2+3+...+n-1+n = O(n^2) memory.
+ *
+ * <p>In order to avoid this, we introduce the concept of nested sets. A nested set is an immutable
+ * data structure that can contain direct members and other nested sets (recursively). Nested sets
+ * are iterable and can be flattened into ordered sets, where the order depends on which
+ * implementation of NestedSet you pick.
+ *
+ * @see TransitiveInfoCollection
+ */
+public interface TransitiveInfoProvider {
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/Util.java b/src/main/java/com/google/devtools/build/lib/analysis/Util.java
new file mode 100644
index 0000000..ee10bf0
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/Util.java
@@ -0,0 +1,64 @@
+// 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.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+/**
+ * Utility methods for use by ConfiguredTarget implementations.
+ */
+public abstract class Util {
+
+ private Util() {}
+
+ //---------- Label and Target related methods
+
+ /**
+ * Returns the workspace-relative path of the specified target (file or rule).
+ *
+ * <p>For example, "//foo/bar:wiz" and "//foo:bar/wiz" both result in "foo/bar/wiz".
+ */
+ public static PathFragment getWorkspaceRelativePath(Target target) {
+ return getWorkspaceRelativePath(target.getLabel());
+ }
+
+ /**
+ * Returns the workspace-relative path of the specified target (file or rule).
+ *
+ * <p>For example, "//foo/bar:wiz" and "//foo:bar/wiz" both result in "foo/bar/wiz".
+ */
+ public static PathFragment getWorkspaceRelativePath(Label label) {
+ return label.getPackageFragment().getRelative(label.getName());
+ }
+
+ /**
+ * Returns the workspace-relative path of the specified target (file or rule),
+ * prepending a prefix and appending a suffix.
+ *
+ * <p>For example, "//foo/bar:wiz" and "//foo:bar/wiz" both result in "foo/bar/wiz".
+ */
+ public static PathFragment getWorkspaceRelativePath(Target target, String prefix, String suffix) {
+ return target.getLabel().getPackageFragment().getRelative(prefix + target.getName() + suffix);
+ }
+
+ /**
+ * Checks if a PathFragment contains a '-'.
+ */
+ public static boolean containsHyphen(PathFragment path) {
+ return path.getPathString().indexOf('-') >= 0;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ViewCreationFailedException.java b/src/main/java/com/google/devtools/build/lib/analysis/ViewCreationFailedException.java
new file mode 100644
index 0000000..ae7dfc5
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ViewCreationFailedException.java
@@ -0,0 +1,31 @@
+// 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;
+
+/**
+ * An exception indicating that there was a problem during the view
+ * construction (loading and analysis phases) for one or more targets, that the
+ * configured target graph could not be successfully constructed, and that
+ * a build cannot be started.
+ */
+public class ViewCreationFailedException extends Exception {
+
+ public ViewCreationFailedException(String message) {
+ super(message);
+ }
+
+ public ViewCreationFailedException(String message, Throwable cause) {
+ super(message + ": " + cause.getMessage(), cause);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/VisibilityProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/VisibilityProvider.java
new file mode 100644
index 0000000..a438145
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/VisibilityProvider.java
@@ -0,0 +1,29 @@
+// 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.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.packages.PackageSpecification;
+
+/**
+ * Provider class for configured targets that have a visibility.
+ */
+public interface VisibilityProvider extends TransitiveInfoProvider {
+
+ /**
+ * Returns the visibility specification.
+ */
+ NestedSet<PackageSpecification> getVisibility();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/VisibilityProviderImpl.java b/src/main/java/com/google/devtools/build/lib/analysis/VisibilityProviderImpl.java
new file mode 100644
index 0000000..01dd06a
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/VisibilityProviderImpl.java
@@ -0,0 +1,36 @@
+// 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.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.packages.PackageSpecification;
+
+/**
+ * Visibility provider implementation.
+ */
+@Immutable
+public final class VisibilityProviderImpl implements VisibilityProvider {
+ private final NestedSet<PackageSpecification> visibility;
+
+ public VisibilityProviderImpl(NestedSet<PackageSpecification> visibility) {
+ this.visibility = visibility;
+ }
+
+ @Override
+ public NestedSet<PackageSpecification> getVisibility() {
+ return visibility;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/WorkspaceStatusAction.java b/src/main/java/com/google/devtools/build/lib/analysis/WorkspaceStatusAction.java
new file mode 100644
index 0000000..5b3bd27
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/WorkspaceStatusAction.java
@@ -0,0 +1,185 @@
+// 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.Splitter;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.actions.AbstractAction;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ArtifactFactory;
+import com.google.devtools.build.lib.actions.ArtifactOwner;
+import com.google.devtools.build.lib.actions.Executor.ActionContext;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * An action writing the workspace status files.
+ *
+ * <p>These files represent information about the environment the build was run in. They are used
+ * by language-specific build info factories to make the data in them available for individual
+ * languages (e.g. by turning them into .h files for C++)
+ *
+ * <p>The format of these files a list of key-value pairs, one for each line. The key and the value
+ * are separated by a space.
+ *
+ * <p>There are two of these files: volatile and stable. Changes in the volatile file do not
+ * cause rebuilds if no other file is changed. This is useful for frequently-changing information
+ * that does not significantly affect the build, e.g. the current time.
+ */
+public abstract class WorkspaceStatusAction extends AbstractAction {
+
+ /**
+ * The type of a workspace status action key.
+ */
+ public enum KeyType {
+ INTEGER,
+ STRING,
+ VERBATIM,
+ }
+
+ /**
+ * Language for keys that should be present in the build info for every language.
+ */
+ // TODO(bazel-team): Once this is released, migrate the only place in the depot to use
+ // the BUILD_USERNAME, BUILD_HOSTNAME and BUILD_DIRECTORY keys instead of BUILD_INFO. Then
+ // language-specific build info keys can be removed.
+ public static final String ALL_LANGUAGES = "*";
+
+ /**
+ * Action context required by the actions that write language-specific workspace status artifacts.
+ */
+ public static interface Context extends ActionContext {
+ ImmutableMap<String, Key> getStableKeys();
+ ImmutableMap<String, Key> getVolatileKeys();
+ }
+
+ /**
+ * A key in the workspace status info file.
+ */
+ public static class Key {
+ private final KeyType type;
+
+ /**
+ * Should be set to ALL_LANGUAGES if the key should be present in the build info of every
+ * language.
+ */
+ private final String language;
+ private final String defaultValue;
+ private final String redactedValue;
+
+ private Key(KeyType type, String language, String defaultValue, String redactedValue) {
+ this.type = type;
+ this.language = language;
+ this.defaultValue = defaultValue;
+ this.redactedValue = redactedValue;
+ }
+
+ public KeyType getType() {
+ return type;
+ }
+
+ public boolean isInLanguage(String language) {
+ return this.language.equals(ALL_LANGUAGES) || this.language.equals(language);
+ }
+
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ public String getRedactedValue() {
+ return redactedValue;
+ }
+
+ public static Key forLanguage(
+ String language, KeyType type, String defaultValue, String redactedValue) {
+ return new Key(type, language, defaultValue, redactedValue);
+ }
+
+ public static Key of(KeyType type, String defaultValue, String redactedValue) {
+ return new Key(type, ALL_LANGUAGES, defaultValue, redactedValue);
+ }
+ }
+
+ /**
+ * Parses the output of the workspace status action.
+ *
+ * <p>The output is a text file with each line representing a workspace status info key.
+ * The key is the part of the line before the first space and should consist of the characters
+ * [A-Z_] (although this is not checked). Everything after the first space is the value.
+ */
+ public static Map<String, String> parseValues(Path file) throws IOException {
+ HashMap<String, String> result = new HashMap<>();
+ Splitter lineSplitter = Splitter.on(" ").limit(2);
+ for (String line : Splitter.on("\n").split(
+ new String(FileSystemUtils.readContentAsLatin1(file)))) {
+ List<String> items = ImmutableList.copyOf(lineSplitter.split(line));
+ if (items.size() != 2) {
+ continue;
+ }
+
+ result.put(items.get(0), items.get(1));
+ }
+
+ return ImmutableMap.copyOf(result);
+ }
+
+ /**
+ * Factory for {@link WorkspaceStatusAction}.
+ */
+ public interface Factory {
+ /**
+ * Creates the workspace status action.
+ *
+ * <p>If the objects returned for two builds are equals, the workspace status action can be
+ * be reused between them. Note that this only applies to the action object itself (the action
+ * will be unconditionally re-executed on every build)
+ */
+ WorkspaceStatusAction createWorkspaceStatusAction(
+ ArtifactFactory artifactFactory, ArtifactOwner artifactOwner, Supplier<UUID> buildId);
+
+ /**
+ * Creates a dummy workspace status map. Used in cases where the build failed, so that part of
+ * the workspace status is nevertheless available.
+ */
+ Map<String, String> createDummyWorkspaceStatus();
+ }
+
+ protected WorkspaceStatusAction(ActionOwner owner,
+ Iterable<Artifact> inputs,
+ Iterable<Artifact> outputs) {
+ super(owner, inputs, outputs);
+ }
+
+ /**
+ * The volatile status artifact containing items that may change even if nothing changed
+ * between the two builds, e.g. current time.
+ */
+ public abstract Artifact getVolatileStatus();
+
+ /**
+ * The stable status artifact containing items that change only if information relevant to the
+ * build changes, e.g. the name of the user running the build or the hostname.
+ */
+ public abstract Artifact getStableStatus();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/AbstractFileWriteAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/AbstractFileWriteAction.java
new file mode 100644
index 0000000..187fc48
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/AbstractFileWriteAction.java
@@ -0,0 +1,142 @@
+// 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.actions;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.AbstractAction;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.syntax.Label;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Abstract Action to write to a file.
+ */
+public abstract class AbstractFileWriteAction extends AbstractAction {
+
+ protected final boolean makeExecutable;
+
+ /**
+ * Creates a new AbstractFileWriteAction instance.
+ *
+ * @param owner the action owner.
+ * @param inputs the Artifacts that this Action depends on
+ * @param output the Artifact that will be created by executing this Action.
+ * @param makeExecutable iff true will change the output file to be
+ * executable.
+ */
+ public AbstractFileWriteAction(ActionOwner owner,
+ Iterable<Artifact> inputs, Artifact output, boolean makeExecutable) {
+ // There is only one output, and it is primary.
+ super(owner, inputs, ImmutableList.of(output));
+ this.makeExecutable = makeExecutable;
+ }
+
+ public boolean makeExecutable() {
+ return makeExecutable;
+ }
+
+ @Override
+ public final void execute(ActionExecutionContext actionExecutionContext)
+ throws ActionExecutionException, InterruptedException {
+ try {
+ getStrategy(actionExecutionContext.getExecutor()).exec(actionExecutionContext.getExecutor(),
+ this, actionExecutionContext.getFileOutErr(), actionExecutionContext);
+ } catch (ExecException e) {
+ throw e.toActionExecutionException(
+ "Writing file for rule '" + Label.print(getOwner().getLabel()) + "'",
+ actionExecutionContext.getExecutor().getVerboseFailures(), this);
+ }
+ afterWrite(actionExecutionContext.getExecutor());
+ }
+
+ /**
+ * Produce a DeterministicWriter that can write the file to an OutputStream deterministically.
+ *
+ * @param eventHandler destination for warning messages. (Note that errors should
+ * still be indicated by throwing an exception; reporter.error() will
+ * not cause action execution to fail.)
+ * @param executor the Executor.
+ * @throws IOException if the content cannot be written to the output stream
+ */
+ public abstract DeterministicWriter newDeterministicWriter(EventHandler eventHandler,
+ Executor executor) throws IOException, InterruptedException, ExecException;
+
+ /**
+ * This hook is called after the File has been successfully written to disk.
+ *
+ * @param executor the Executor.
+ */
+ protected void afterWrite(Executor executor) {
+ }
+
+ // We're mainly doing I/O, so estimate very low CPU usage, e.g. 1%. Just a guess.
+ private static final ResourceSet DEFAULT_FILEWRITE_LOCAL_ACTION_RESOURCE_SET =
+ new ResourceSet(/*memoryMb=*/0.0, /*cpuUsage=*/0.01, /*ioUsage=*/0.2);
+
+ @Override
+ public ResourceSet estimateResourceConsumption(Executor executor) {
+ return executor.getContext(FileWriteActionContext.class).estimateResourceConsumption(this);
+ }
+
+ public ResourceSet estimateResourceConsumptionLocal() {
+ return DEFAULT_FILEWRITE_LOCAL_ACTION_RESOURCE_SET;
+ }
+
+ @Override
+ public String getMnemonic() {
+ return "FileWrite";
+ }
+
+ @Override
+ protected String getRawProgressMessage() {
+ return "Writing " + (makeExecutable ? "script " : "file ")
+ + Iterables.getOnlyElement(getOutputs()).prettyPrint();
+ }
+
+ /**
+ * Whether the file write can be generated remotely. If the file is consumed in Blaze
+ * unconditionally, it doesn't make sense to run remotely.
+ */
+ public boolean isRemotable() {
+ return true;
+ }
+
+ @Override
+ public final String describeStrategy(Executor executor) {
+ return executor.getContext(FileWriteActionContext.class).strategyLocality(this);
+ }
+
+ private FileWriteActionContext getStrategy(Executor executor) {
+ return executor.getContext(FileWriteActionContext.class);
+ }
+
+ /**
+ * A deterministic writer writes bytes to an output stream. The same byte stream is written
+ * on every invocation of writeOutputFile().
+ */
+ public interface DeterministicWriter {
+ public void writeOutputFile(OutputStream out) throws IOException;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/ActionConstructionContext.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/ActionConstructionContext.java
new file mode 100644
index 0000000..b7461e5
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/ActionConstructionContext.java
@@ -0,0 +1,37 @@
+// 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.actions;
+
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.packages.Rule;
+
+/**
+ * A temporary interface to allow migration from RuleConfiguredTarget to RuleContext. It bundles
+ * the items commonly needed to construct action instances.
+ */
+public interface ActionConstructionContext {
+ /** The rule for which the actions are constructed. */
+ Rule getRule();
+
+ /** Returns the action owner that should be used for actions. */
+ ActionOwner getActionOwner();
+
+ /** Returns the {@link BuildConfiguration} for which the given rule is analyzed. */
+ BuildConfiguration getConfiguration();
+
+ /** The current analysis environment. */
+ AnalysisEnvironment getAnalysisEnvironment();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/BinaryFileWriteAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/BinaryFileWriteAction.java
new file mode 100644
index 0000000..b9a2ea5
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/BinaryFileWriteAction.java
@@ -0,0 +1,90 @@
+// 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.actions;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.io.ByteSource;
+import com.google.common.io.ByteStreams;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.util.Fingerprint;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Action to write a binary file.
+ */
+public final class BinaryFileWriteAction extends AbstractFileWriteAction {
+
+ private static final String GUID = "eeee07fe-4b40-11e4-82d6-eba0b4f713e2";
+
+ private final ByteSource source;
+
+ /**
+ * Creates a new BinaryFileWriteAction instance without inputs.
+ *
+ * @param owner the action owner.
+ * @param output the Artifact that will be created by executing this Action.
+ * @param source a source of bytes that will be written to the file.
+ * @param makeExecutable iff true will change the output file to be executable.
+ */
+ public BinaryFileWriteAction(
+ ActionOwner owner, Artifact output, ByteSource source, boolean makeExecutable) {
+ super(owner, /*inputs=*/Artifact.NO_ARTIFACTS, output, makeExecutable);
+ this.source = Preconditions.checkNotNull(source);
+ }
+
+ @VisibleForTesting
+ public ByteSource getSource() {
+ return source;
+ }
+
+ @Override
+ public DeterministicWriter newDeterministicWriter(EventHandler eventHandler, Executor executor) {
+ return new DeterministicWriter() {
+ @Override
+ public void writeOutputFile(OutputStream out) throws IOException {
+ try (InputStream in = source.openStream()) {
+ ByteStreams.copy(in, out);
+ }
+ out.flush();
+ }
+ };
+ }
+
+ @Override
+ protected String computeKey() {
+ Fingerprint f = new Fingerprint();
+ f.addString(GUID);
+ f.addString(String.valueOf(makeExecutable));
+
+ try (InputStream in = source.openStream()) {
+ byte[] buffer = new byte[512];
+ int amountRead;
+ while ((amountRead = in.read(buffer)) != -1) {
+ f.addBytes(buffer, 0, amountRead);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ return f.hexDigestAndReset();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/CommandLine.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/CommandLine.java
new file mode 100644
index 0000000..ddadc25
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/CommandLine.java
@@ -0,0 +1,123 @@
+// 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.actions;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.collect.CollectionUtils;
+
+/**
+ * A representation of a command line to be executed by a SpawnAction.
+ */
+public abstract class CommandLine {
+ /**
+ * Returns the command line.
+ */
+ public abstract Iterable<String> arguments();
+
+ /**
+ * Returns whether the command line represents a shell command with the given shell executable.
+ * This is used to give better error messages.
+ *
+ * <p>By default, this method returns false.
+ */
+ public boolean isShellCommand() {
+ return false;
+ }
+
+ /**
+ * A default implementation of a command line backed by a copy of the given list of arguments.
+ */
+ static CommandLine ofInternal(Iterable<String> arguments, final boolean isShellCommand) {
+ final Iterable<String> immutableArguments = CollectionUtils.makeImmutable(arguments);
+ return new CommandLine() {
+ @Override
+ public Iterable<String> arguments() {
+ return immutableArguments;
+ }
+
+ @Override
+ public boolean isShellCommand() {
+ return isShellCommand;
+ }
+ };
+ }
+
+ /**
+ * Returns a {@link CommandLine} backed by a copy of the given list of arguments.
+ */
+ public static CommandLine of(Iterable<String> arguments, final boolean isShellCommand) {
+ final Iterable<String> immutableArguments = CollectionUtils.makeImmutable(arguments);
+ return new CommandLine() {
+ @Override
+ public Iterable<String> arguments() {
+ return immutableArguments;
+ }
+
+ @Override
+ public boolean isShellCommand() {
+ return isShellCommand;
+ }
+ };
+ }
+
+ /**
+ * Returns a {@link CommandLine} that is constructed by prepending the {@code executableArgs} to
+ * {@code commandLine}.
+ */
+ static CommandLine ofMixed(final ImmutableList<String> executableArgs,
+ final CommandLine commandLine, final boolean isShellCommand) {
+ Preconditions.checkState(!executableArgs.isEmpty());
+ return new CommandLine() {
+ @Override
+ public Iterable<String> arguments() {
+ return Iterables.concat(executableArgs, commandLine.arguments());
+ }
+
+ @Override
+ public boolean isShellCommand() {
+ return isShellCommand;
+ }
+ };
+ }
+
+ /**
+ * Returns a {@link CommandLine} with {@link CharSequence} arguments. This can be useful to create
+ * memory efficient command lines with {@link com.google.devtools.build.lib.util.LazyString}s.
+ */
+ public static CommandLine ofCharSequences(final ImmutableList<CharSequence> arguments) {
+ return new CommandLine() {
+ @Override
+ public Iterable<String> arguments() {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ for (CharSequence arg : arguments) {
+ builder.add(arg.toString());
+ }
+ return builder.build();
+ }
+ };
+ }
+
+ /**
+ * This helps when debugging Blaze code that uses {@link CommandLine}s, as you can see their
+ * content directly in the variable inspector.
+ */
+ @Override
+ public String toString() {
+ return Joiner.on(' ').join(arguments());
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java
new file mode 100644
index 0000000..d358f0b
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java
@@ -0,0 +1,358 @@
+// 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.actions;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.collect.CollectionUtils;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A customizable, serializable class for building memory efficient command lines.
+ */
+@Immutable
+public final class CustomCommandLine extends CommandLine {
+
+ private abstract static class ArgvFragment {
+ abstract void eval(ImmutableList.Builder<String> builder);
+ }
+
+ // It's better to avoid anonymous classes if we want to serialize command lines
+
+ private static final class ObjectArg extends ArgvFragment {
+ private final Object arg;
+
+ private ObjectArg(Object arg) {
+ this.arg = arg;
+ }
+
+ @Override
+ void eval(ImmutableList.Builder<String> builder) {
+ builder.add(arg.toString());
+ }
+ }
+
+ private static final class JoinExecPathsArg extends ArgvFragment {
+
+ private final String delimiter;
+ private final Iterable<Artifact> artifacts;
+
+ private JoinExecPathsArg(String delimiter, Iterable<Artifact> artifacts) {
+ this.delimiter = delimiter;
+ this.artifacts = CollectionUtils.makeImmutable(artifacts);
+ }
+
+ @Override
+ void eval(ImmutableList.Builder<String> builder) {
+ builder.add(Artifact.joinExecPaths(delimiter, artifacts));
+ }
+ }
+
+ private static final class PathWithTemplateArg extends ArgvFragment {
+
+ private final String template;
+ private final PathFragment[] paths;
+
+ private PathWithTemplateArg(String template, PathFragment... paths) {
+ this.template = template;
+ this.paths = paths;
+ }
+
+ @Override
+ void eval(ImmutableList.Builder<String> builder) {
+ // PathFragment.toString() uses getPathString()
+ builder.add(String.format(template, (Object[]) paths));
+ }
+ }
+
+ // TODO(bazel-team): CustomArgv and CustomMultiArgv is going to be difficult to expose
+ // in Skylark. Maybe we can get rid of them by refactoring JavaCompileAction. It also
+ // raises immutability / serialization issues.
+ /**
+ * Custom Java code producing a String argument. Usage of this class is discouraged.
+ */
+ public abstract static class CustomArgv extends ArgvFragment {
+
+ @Override
+ void eval(ImmutableList.Builder<String> builder) {
+ builder.add(argv());
+ }
+
+ public abstract String argv();
+ }
+
+ /**
+ * Custom Java code producing a List of String arguments. Usage of this class is discouraged.
+ */
+ public abstract static class CustomMultiArgv extends ArgvFragment {
+
+ @Override
+ void eval(ImmutableList.Builder<String> builder) {
+ builder.addAll(argv());
+ }
+
+ public abstract Iterable<String> argv();
+ }
+
+ private static final class JoinPathsArg extends ArgvFragment {
+
+ private final String delimiter;
+ private final Iterable<PathFragment> paths;
+
+ private JoinPathsArg(String delimiter, Iterable<PathFragment> paths) {
+ this.delimiter = delimiter;
+ this.paths = CollectionUtils.makeImmutable(paths);
+ }
+
+ @Override
+ void eval(ImmutableList.Builder<String> builder) {
+ builder.add(Joiner.on(delimiter).join(paths));
+ }
+ }
+
+ /**
+ * Arguments that intersperse strings between the items in a sequence. There are two forms of
+ * interspersing, and either may be used by this implementation:
+ * <ul>
+ * <li>before each - a string is added before each item in a sequence. e.g.
+ * {@code -f foo -f bar -f baz}
+ * <li>format each - a format string is used to format each item in a sequence. e.g.
+ * {@code -I/foo -I/bar -I/baz} for the format {@code "-I%s"}
+ * </ul>
+ *
+ * <p>This class could be used both with both the "before" and "format" features at the same
+ * time, but this is probably more confusion than it is worth. If you need this functionality,
+ * consider using "before" only but storing the strings pre-formated in a {@link NestedSet}.
+ */
+ private static final class InterspersingArgs extends ArgvFragment {
+ private final Iterable<?> sequence;
+ private final String beforeEach;
+ private final String formatEach;
+
+ /**
+ * Do not call from outside this class because this does not guarantee that {@code sequence} is
+ * immutable.
+ */
+ private InterspersingArgs(Iterable<?> sequence, String beforeEach, String formatEach) {
+ this.sequence = sequence;
+ this.beforeEach = beforeEach;
+ this.formatEach = formatEach;
+ }
+
+ static InterspersingArgs fromStrings(
+ Iterable<?> sequence, String beforeEach, String formatEach) {
+ return new InterspersingArgs(
+ CollectionUtils.makeImmutable(sequence), beforeEach, formatEach);
+ }
+
+ static InterspersingArgs fromExecPaths(
+ Iterable<Artifact> sequence, String beforeEach, String formatEach) {
+ return new InterspersingArgs(
+ Artifact.toExecPaths(CollectionUtils.makeImmutable(sequence)), beforeEach, formatEach);
+ }
+
+ @Override
+ void eval(ImmutableList.Builder<String> builder) {
+ for (Object item : sequence) {
+ if (item == null) {
+ continue;
+ }
+
+ if (beforeEach != null) {
+ builder.add(beforeEach);
+ }
+ String arg = item.toString();
+ if (formatEach != null) {
+ arg = String.format(formatEach, arg);
+ }
+ builder.add(arg);
+ }
+ }
+ }
+
+ /**
+ * A Builder class for CustomCommandLine with the appropriate methods.
+ *
+ * <p>{@link Iterable} instances passed to {@code add*} methods will be stored internally as
+ * collections that are known to be immutable copies. This means that any {@link Iterable} that is
+ * not a {@link NestedSet} or {@link ImmutableList} may be copied.
+ *
+ * <p>{@code addFormatEach*} methods take an {@link Iterable} but use these as arguments to
+ * {@link String#format(String, Object...)} with a certain constant format string. For instance,
+ * if {@code format} is {@code "-I%s"}, then the final arguments may be
+ * {@code -Ifoo -Ibar -Ibaz}
+ *
+ * <p>{@code addBeforeEach*} methods take an {@link Iterable} but insert a certain {@link String}
+ * once before each element in the string, meaning the total number of elements added is twice the
+ * length of the {@link Iterable}. For instance: {@code -f foo -f bar -f baz}
+ */
+ public static final class Builder {
+
+ private final List<ArgvFragment> arguments = new ArrayList<>();
+
+ public Builder add(CharSequence arg) {
+ if (arg != null) {
+ arguments.add(new ObjectArg(arg));
+ }
+ return this;
+ }
+
+ public Builder add(Label arg) {
+ if (arg != null) {
+ arguments.add(new ObjectArg(arg));
+ }
+ return this;
+ }
+
+ public Builder add(String arg, Iterable<String> args) {
+ if (arg != null && args != null) {
+ arguments.add(new ObjectArg(arg));
+ arguments.add(InterspersingArgs.fromStrings(args, /*beforeEach=*/null, "%s"));
+ }
+ return this;
+ }
+
+ public Builder add(Iterable<String> args) {
+ if (args != null) {
+ arguments.add(InterspersingArgs.fromStrings(args, /*beforeEach=*/null, "%s"));
+ }
+ return this;
+ }
+
+ public Builder addExecPath(String arg, Artifact artifact) {
+ if (arg != null && artifact != null) {
+ arguments.add(new ObjectArg(arg));
+ arguments.add(new ObjectArg(artifact.getExecPath()));
+ }
+ return this;
+ }
+
+ public Builder addExecPaths(String arg, Iterable<Artifact> artifacts) {
+ if (arg != null && artifacts != null) {
+ arguments.add(new ObjectArg(arg));
+ arguments.add(InterspersingArgs.fromExecPaths(artifacts, /*beforeEach=*/null, "%s"));
+ }
+ return this;
+ }
+
+ public Builder addExecPaths(Iterable<Artifact> artifacts) {
+ if (artifacts != null) {
+ arguments.add(InterspersingArgs.fromExecPaths(artifacts, /*beforeEach=*/null, "%s"));
+ }
+ return this;
+ }
+
+ public Builder addJoinExecPaths(String arg, String delimiter, Iterable<Artifact> artifacts) {
+ if (arg != null && artifacts != null) {
+ arguments.add(new ObjectArg(arg));
+ arguments.add(new JoinExecPathsArg(delimiter, artifacts));
+ }
+ return this;
+ }
+
+ public Builder addPath(PathFragment path) {
+ if (path != null) {
+ arguments.add(new ObjectArg(path));
+ }
+ return this;
+ }
+
+ public Builder addPaths(String template, PathFragment... path) {
+ if (template != null && path != null) {
+ arguments.add(new PathWithTemplateArg(template, path));
+ }
+ return this;
+ }
+
+ public Builder addJoinPaths(String delimiter, Iterable<PathFragment> paths) {
+ if (delimiter != null && paths != null) {
+ arguments.add(new JoinPathsArg(delimiter, paths));
+ }
+ return this;
+ }
+
+ public Builder addBeforeEachPath(String repeated, Iterable<PathFragment> paths) {
+ if (repeated != null && paths != null) {
+ arguments.add(InterspersingArgs.fromStrings(paths, repeated, "%s"));
+ }
+ return this;
+ }
+
+ public Builder addBeforeEach(String repeated, Iterable<String> strings) {
+ if (repeated != null && strings != null) {
+ arguments.add(InterspersingArgs.fromStrings(strings, repeated, "%s"));
+ }
+ return this;
+ }
+
+ public Builder addBeforeEachExecPath(String repeated, Iterable<Artifact> artifacts) {
+ if (repeated != null && artifacts != null) {
+ arguments.add(InterspersingArgs.fromExecPaths(artifacts, repeated, "%s"));
+ }
+ return this;
+ }
+
+ public Builder addFormatEach(String format, Iterable<String> strings) {
+ if (format != null && strings != null) {
+ arguments.add(InterspersingArgs.fromStrings(strings, /*beforeEach=*/null, format));
+ }
+ return this;
+ }
+
+ public Builder add(CustomArgv arg) {
+ if (arg != null) {
+ arguments.add(arg);
+ }
+ return this;
+ }
+
+ public Builder add(CustomMultiArgv arg) {
+ if (arg != null) {
+ arguments.add(arg);
+ }
+ return this;
+ }
+
+ public CustomCommandLine build() {
+ return new CustomCommandLine(arguments);
+ }
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ private final ImmutableList<ArgvFragment> arguments;
+
+ private CustomCommandLine(List<ArgvFragment> arguments) {
+ this.arguments = ImmutableList.copyOf(arguments);
+ }
+
+ @Override
+ public Iterable<String> arguments() {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ for (ArgvFragment arg : arguments) {
+ arg.eval(builder);
+ }
+ return builder.build();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/ExecutableSymlinkAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/ExecutableSymlinkAction.java
new file mode 100644
index 0000000..376d9b8
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/ExecutableSymlinkAction.java
@@ -0,0 +1,66 @@
+// 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.actions;
+
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.vfs.Path;
+
+import java.io.IOException;
+
+/**
+ * Action to create an executable symbolic link. It includes additional
+ * validation that symlink target is indeed an executable file.
+ */
+public final class ExecutableSymlinkAction extends SymlinkAction {
+
+ public ExecutableSymlinkAction(ActionOwner owner, Artifact input, Artifact output) {
+ super(owner, input, output, "Symlinking " + owner.getLabel());
+ }
+
+ @Override
+ public void execute(ActionExecutionContext actionExecutionContext)
+ throws ActionExecutionException {
+ Path inputPath = actionExecutionContext.getExecutor().getExecRoot().getRelative(
+ getInputPath());
+ try {
+ // Validate that input path is a file with the executable bit is set.
+ if (!inputPath.isFile()) {
+ throw new ActionExecutionException(
+ "'" + Iterables.getOnlyElement(getInputs()).prettyPrint() + "' is not a file", this,
+ false);
+ }
+ if (!inputPath.isExecutable()) {
+ throw new ActionExecutionException("failed to create symbolic link '"
+ + Iterables.getOnlyElement(getOutputs()).prettyPrint()
+ + "': file '" + Iterables.getOnlyElement(getInputs()).prettyPrint()
+ + "' is not executable", this, false);
+ }
+ } catch (IOException e) {
+ throw new ActionExecutionException("failed to create symbolic link '"
+ + Iterables.getOnlyElement(getOutputs()).prettyPrint()
+ + "' to the '" + Iterables.getOnlyElement(getInputs()).prettyPrint()
+ + "' due to I/O error: " + e.getMessage(), e, this, false);
+ }
+
+ super.execute(actionExecutionContext);
+ }
+
+ @Override
+ public String getMnemonic() { return "ExecutableSymlink"; }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/FileWriteAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/FileWriteAction.java
new file mode 100644
index 0000000..1128617
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/FileWriteAction.java
@@ -0,0 +1,145 @@
+// 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.actions;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.util.Fingerprint;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collection;
+
+/**
+ * Action to write to a file.
+ * <p>TODO(bazel-team): Choose a better name to distinguish this class from
+ * {@link BinaryFileWriteAction}.
+ */
+public class FileWriteAction extends AbstractFileWriteAction {
+
+ private static final String GUID = "332877c7-ca9f-4731-b387-54f620408522";
+
+ /**
+ * We keep it as a CharSequence for memory-efficiency reasons. The toString()
+ * method of the object represents the content of the file.
+ *
+ * <p>For example, this allows us to keep a {@code List<Artifact>} wrapped
+ * in a {@code LazyString} instead of the string representation of the concatenation.
+ * This saves memory because the Artifacts are shared objects but the
+ * resulting String is not.
+ */
+ private final CharSequence fileContents;
+
+ /**
+ * Creates a new FileWriteAction instance without inputs using UTF8 encoding.
+ *
+ * @param owner the action owner.
+ * @param output the Artifact that will be created by executing this Action.
+ * @param fileContents the contents to be written to the file.
+ * @param makeExecutable iff true will change the output file to be
+ * executable.
+ */
+ public FileWriteAction(ActionOwner owner, Artifact output, CharSequence fileContents,
+ boolean makeExecutable) {
+ this(owner, Artifact.NO_ARTIFACTS, output, fileContents, makeExecutable);
+ }
+
+ /**
+ * Creates a new FileWriteAction instance using UTF8 encoding.
+ *
+ * @param owner the action owner.
+ * @param inputs the Artifacts that this Action depends on
+ * @param output the Artifact that will be created by executing this Action.
+ * @param fileContents the contents to be written to the file.
+ * @param makeExecutable iff true will change the output file to be
+ * executable.
+ */
+ public FileWriteAction(ActionOwner owner, Collection<Artifact> inputs,
+ Artifact output, CharSequence fileContents, boolean makeExecutable) {
+ super(owner, inputs, output, makeExecutable);
+ this.fileContents = fileContents;
+ }
+
+ /**
+ * Creates a new FileWriteAction instance using UTF8 encoding.
+ *
+ * @param owner the action owner.
+ * @param inputs the Artifacts that this Action depends on
+ * @param output the Artifact that will be created by executing this Action.
+ * @param makeExecutable iff true will change the output file to be
+ * executable.
+ */
+ protected FileWriteAction(ActionOwner owner, Collection<Artifact> inputs,
+ Artifact output, boolean makeExecutable) {
+ this(owner, inputs, output, "", makeExecutable);
+ }
+
+ public String getFileContents() {
+ return fileContents.toString();
+ }
+
+ /**
+ * Create a DeterministicWriter for the content of the output file as provided by
+ * {@link #getFileContents()}.
+ */
+ @Override
+ public DeterministicWriter newDeterministicWriter(EventHandler eventHandler,
+ Executor executor) {
+ return new DeterministicWriter() {
+ @Override
+ public void writeOutputFile(OutputStream out) throws IOException {
+ out.write(getFileContents().getBytes(UTF_8));
+ }
+ };
+ }
+
+ /**
+ * Computes the Action key for this action by computing the fingerprint for
+ * the file contents.
+ */
+ @Override
+ protected String computeKey() {
+ Fingerprint f = new Fingerprint();
+ f.addString(GUID);
+ f.addString(String.valueOf(makeExecutable));
+ f.addString(getFileContents());
+ return f.hexDigestAndReset();
+ }
+
+ /**
+ * Creates a FileWriteAction to write contents to the resulting artifact
+ * fileName in the genfiles root underneath the package path.
+ *
+ * @param ruleContext the ruleContext that will own the action of creating this file.
+ * @param fileName name of the file to create.
+ * @param contents data to write to file.
+ * @param executable flags that file should be marked executable.
+ * @return Artifact describing the file to create.
+ */
+ public static Artifact createFile(RuleContext ruleContext,
+ String fileName, CharSequence contents, boolean executable) {
+ Artifact scriptFileArtifact = ruleContext.getAnalysisEnvironment().getDerivedArtifact(
+ ruleContext.getTarget().getLabel().getPackageFragment().getRelative(fileName),
+ ruleContext.getConfiguration().getGenfilesDirectory());
+ ruleContext.registerAction(new FileWriteAction(
+ ruleContext.getActionOwner(), scriptFileArtifact, contents, executable));
+ return scriptFileArtifact;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionContext.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionContext.java
new file mode 100644
index 0000000..7e10331
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/FileWriteActionContext.java
@@ -0,0 +1,44 @@
+// 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.actions;
+
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.Executor.ActionContext;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.util.io.FileOutErr;
+
+/**
+ * The action context for {@link AbstractFileWriteAction} instances (technically instances of
+ * subclasses).
+ */
+public interface FileWriteActionContext extends ActionContext {
+
+ /**
+ * Performs all the setup and then calls back into the action to write the data.
+ */
+ void exec(Executor executor, AbstractFileWriteAction action, FileOutErr outErr,
+ ActionExecutionContext actionExecutionContext) throws ExecException, InterruptedException;
+
+ /**
+ * Returns the estimated resource consumption of the action.
+ */
+ ResourceSet estimateResourceConsumption(AbstractFileWriteAction action);
+
+ /**
+ * Returns where the action actually runs.
+ */
+ String strategyLocality(AbstractFileWriteAction action);
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileHelper.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileHelper.java
new file mode 100644
index 0000000..e7869e4
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileHelper.java
@@ -0,0 +1,158 @@
+// 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.actions;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ParameterFile;
+import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A command-line implementation that wraps another command line and puts the arguments in a
+ * parameter file if necessary
+ *
+ * <p>The Linux kernel has a limit for the command line length, and that can be easily reached
+ * if, for example, a command is listing all its inputs on the command line.
+ */
+@Immutable
+public final class ParamFileHelper {
+
+ /**
+ * Returns a params file artifact or null for a given command description.
+ *
+ * <p>Returns null if parameter files are not to be used according to paramFileInfo, or if the
+ * command line is short enough that a parameter file is not needed.
+ *
+ * <p>Make sure to add the returned artifact (if not null) as an input of the corresponding
+ * action.
+ *
+ * @param executableArgs leading arguments that should never be wrapped in a parameter file
+ * @param arguments arguments to the command (in addition to executableArgs), OR
+ * @param commandLine a {@link CommandLine} that provides the arguments (in addition to
+ * executableArgs)
+ * @param paramFileInfo parameter file information
+ * @param configuration the configuration
+ * @param analysisEnvironment the analysis environment
+ * @param outputs outputs of the action (used to construct a filename for the params file)
+ */
+ public static Artifact getParamsFile(
+ List<String> executableArgs,
+ @Nullable Iterable<String> arguments,
+ @Nullable CommandLine commandLine,
+ @Nullable ParamFileInfo paramFileInfo,
+ BuildConfiguration configuration,
+ AnalysisEnvironment analysisEnvironment,
+ Iterable<Artifact> outputs) {
+ if (paramFileInfo == null ||
+ getParamFileSize(executableArgs, arguments, commandLine)
+ < configuration.getMinParamFileSize()) {
+ return null;
+ }
+
+ PathFragment paramFilePath = ParameterFile.derivePath(
+ Iterables.getFirst(outputs, null).getRootRelativePath());
+ return analysisEnvironment.getDerivedArtifact(paramFilePath, configuration.getBinDirectory());
+ }
+
+ /**
+ * Creates a command line using an external params file.
+ *
+ * <p>Call this with the result of {@link #getParamsFile} if it is not null.
+ *
+ * @param executableArgs leading arguments that should never be wrapped in a parameter file
+ * @param arguments arguments to the command (in addition to executableArgs), OR
+ * @param commandLine a {@link CommandLine} that provides the arguments (in addition to
+ * executableArgs)
+ * @param isShellCommand true if this is a shell command
+ * @param owner owner of the action
+ * @param paramFileInfo parameter file information
+ */
+ public static CommandLine createWithParamsFile(
+ List<String> executableArgs,
+ @Nullable Iterable<String> arguments,
+ @Nullable CommandLine commandLine,
+ boolean isShellCommand,
+ ActionOwner owner,
+ List<Action> requiredActions,
+ ParamFileInfo paramFileInfo,
+ Artifact parameterFile) {
+ Preconditions.checkNotNull(parameterFile);
+ if (commandLine != null && arguments != null && !Iterables.isEmpty(arguments)) {
+ throw new IllegalStateException("must provide either commandLine or arguments: " + arguments);
+ }
+
+ CommandLine paramFileContents =
+ (commandLine != null) ? commandLine : CommandLine.ofInternal(arguments, false);
+ requiredActions.add(new ParameterFileWriteAction(owner, parameterFile, paramFileContents,
+ paramFileInfo.getFileType(), paramFileInfo.getCharset()));
+
+ String pathWithFlag = paramFileInfo.getFlag() + parameterFile.getExecPathString();
+ Iterable<String> commandArgv = Iterables.concat(executableArgs, ImmutableList.of(pathWithFlag));
+ return CommandLine.ofInternal(commandArgv, isShellCommand);
+ }
+
+ /**
+ * Creates a command line without using a params file.
+ *
+ * <p>Call this if {@link #getParamsFile} returns null.
+ *
+ * @param executableArgs leading arguments that should never be wrapped in a parameter file
+ * @param arguments arguments to the command (in addition to executableArgs), OR
+ * @param commandLine a {@link CommandLine} that provides the arguments (in addition to
+ * executableArgs)
+ * @param isShellCommand true if this is a shell command
+ */
+ public static CommandLine createWithoutParamsFile(List<String> executableArgs,
+ Iterable<String> arguments, CommandLine commandLine, boolean isShellCommand) {
+ if (commandLine == null) {
+ Iterable<String> commandArgv = Iterables.concat(executableArgs, arguments);
+ return CommandLine.ofInternal(commandArgv, isShellCommand);
+ }
+
+ if (executableArgs.isEmpty()) {
+ return commandLine;
+ }
+
+ return CommandLine.ofMixed(ImmutableList.copyOf(executableArgs), commandLine, isShellCommand);
+ }
+
+ /**
+ * Estimates the params file size for the given arguments.
+ */
+ private static int getParamFileSize(
+ List<String> executableArgs, Iterable<String> arguments, CommandLine commandLine) {
+ Iterable<String> actualArguments = (commandLine != null) ? commandLine.arguments() : arguments;
+ return getParamFileSize(executableArgs) + getParamFileSize(actualArguments);
+ }
+
+ private static int getParamFileSize(Iterable<String> args) {
+ int size = 0;
+ for (String s : args) {
+ size += s.length() + 1;
+ }
+ return size;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileInfo.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileInfo.java
new file mode 100644
index 0000000..ae00181
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/ParamFileInfo.java
@@ -0,0 +1,79 @@
+// 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.actions;
+
+import com.google.common.base.Preconditions;
+import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
+
+import java.nio.charset.Charset;
+import java.util.Objects;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * An object that encapsulates how a params file should be constructed: what is the filetype,
+ * what charset to use and what prefix (typically "@") to use.
+ */
+@Immutable
+public final class ParamFileInfo {
+ private final ParameterFileType fileType;
+ private final Charset charset;
+ private final String flag;
+
+ public ParamFileInfo(ParameterFileType fileType, Charset charset, String flag) {
+ this.fileType = Preconditions.checkNotNull(fileType);
+ this.charset = Preconditions.checkNotNull(charset);
+ this.flag = Preconditions.checkNotNull(flag);
+ }
+
+ /**
+ * Returns the file type.
+ */
+ public ParameterFileType getFileType() {
+ return fileType;
+ }
+
+ /**
+ * Returns the charset.
+ */
+ public Charset getCharset() {
+ return charset;
+ }
+
+ /**
+ * Returns the prefix for the params filename on the command line (typically "@").
+ */
+ public String getFlag() {
+ return flag;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(charset, flag, fileType);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof ParamFileInfo)) {
+ return false;
+ }
+ ParamFileInfo other = (ParamFileInfo) obj;
+ return fileType.equals(other.fileType) && charset.equals(other.charset)
+ && flag.equals(other.flag);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/ParameterFileWriteAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/ParameterFileWriteAction.java
new file mode 100644
index 0000000..fd10e2b
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/ParameterFileWriteAction.java
@@ -0,0 +1,122 @@
+// 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.actions;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.util.ShellEscaper;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
+
+/**
+ * Action to write a parameter file for a {@link CommandLine}.
+ */
+public final class ParameterFileWriteAction extends AbstractFileWriteAction {
+
+ private static final String GUID = "45f678d8-e395-401e-8446-e795ccc6361f";
+
+ private final CommandLine commandLine;
+ private final ParameterFileType type;
+ private final Charset charset;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param owner the action owner
+ * @param output the Artifact that will be created by executing this Action
+ * @param commandLine the contents to be written to the file
+ * @param type the type of the file
+ * @param charset the charset of the file
+ */
+ public ParameterFileWriteAction(ActionOwner owner, Artifact output, CommandLine commandLine,
+ ParameterFileType type, Charset charset) {
+ super(owner, ImmutableList.<Artifact>of(), output, false);
+ this.commandLine = commandLine;
+ this.type = type;
+ this.charset = charset;
+ }
+
+ /**
+ * Returns the list of options written to the parameter file. Don't use this
+ * method outside tests - the list is often huge, resulting in significant
+ * garbage collection overhead.
+ */
+ @VisibleForTesting
+ public Iterable<String> getContents() {
+ return commandLine.arguments();
+ }
+
+ @Override
+ public DeterministicWriter newDeterministicWriter(EventHandler eventHandler, Executor executor) {
+ return new DeterministicWriter() {
+ @Override
+ public void writeOutputFile(OutputStream out) throws IOException {
+ switch (type) {
+ case SHELL_QUOTED :
+ writeContentQuoted(out);
+ break;
+ case UNQUOTED :
+ writeContentUnquoted(out);
+ break;
+ default :
+ throw new AssertionError();
+ }
+ }
+ };
+ }
+
+ /**
+ * Writes the arguments from the list into the parameter file.
+ */
+ private void writeContentUnquoted(OutputStream outputStream) throws IOException {
+ OutputStreamWriter out = new OutputStreamWriter(outputStream, charset);
+ for (String line : commandLine.arguments()) {
+ out.write(line);
+ out.write('\n');
+ }
+ out.flush();
+ }
+
+ /**
+ * Writes the arguments from the list into the parameter file with shell
+ * quoting (if required).
+ */
+ private void writeContentQuoted(OutputStream outputStream) throws IOException {
+ OutputStreamWriter out = new OutputStreamWriter(outputStream, charset);
+ for (String line : ShellEscaper.escapeAll(commandLine.arguments())) {
+ out.write(line);
+ out.write('\n');
+ }
+ out.flush();
+ }
+
+ @Override
+ protected String computeKey() {
+ Fingerprint f = new Fingerprint();
+ f.addString(GUID);
+ f.addString(String.valueOf(makeExecutable));
+ f.addStrings(commandLine.arguments());
+ return f.hexDigestAndReset();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java
new file mode 100644
index 0000000..f51c917
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java
@@ -0,0 +1,874 @@
+// 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.actions;
+
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.actions.AbstractAction;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.ActionInput;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.BaseSpawn;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.actions.Spawn;
+import com.google.devtools.build.lib.actions.SpawnActionContext;
+import com.google.devtools.build.lib.actions.extra.ExtraActionInfo;
+import com.google.devtools.build.lib.actions.extra.SpawnInfo;
+import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
+import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.collect.CollectionUtils;
+import com.google.devtools.build.lib.collect.IterablesChain;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.util.ShellEscaper;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.protobuf.GeneratedMessage.GeneratedExtension;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.CheckReturnValue;
+
+/**
+ * An Action representing an arbitrary subprocess to be forked and exec'd.
+ */
+public class SpawnAction extends AbstractAction {
+ private static class ExtraActionInfoSupplier<T> {
+ private final GeneratedExtension<ExtraActionInfo, T> extension;
+ private final T value;
+
+ private ExtraActionInfoSupplier(
+ GeneratedExtension<ExtraActionInfo, T> extension,
+ T value) {
+ this.extension = extension;
+ this.value = value;
+ }
+
+ void extend(ExtraActionInfo.Builder builder) {
+ builder.setExtension(extension, value);
+ }
+ }
+
+ private static final String GUID = "ebd6fce3-093e-45ee-adb6-bf513b602f0d";
+
+ private final CommandLine argv;
+
+ private final String progressMessage;
+ private final String mnemonic;
+ // entries are (directory for remote execution, Artifact)
+ private final Map<PathFragment, Artifact> inputManifests;
+
+ private final ResourceSet resourceSet;
+ private final ImmutableMap<String, String> environment;
+ private final ImmutableMap<String, String> executionInfo;
+
+ private final ExtraActionInfoSupplier<?> extraActionInfoSupplier;
+
+ /**
+ * Constructs a SpawnAction using direct initialization arguments.
+ * <p>
+ * All collections provided must not be subsequently modified.
+ *
+ * @param owner the owner of the Action.
+ * @param inputs the set of all files potentially read by this action; must
+ * not be subsequently modified.
+ * @param outputs the set of all files written by this action; must not be
+ * subsequently modified.
+ * @param resourceSet the resources consumed by executing this Action
+ * @param environment the map of environment variables.
+ * @param argv the command line to execute. This is merely a list of options
+ * to the executable, and is uninterpreted by the build tool for the
+ * purposes of dependency checking; typically it may include the names
+ * of input and output files, but this is not necessary.
+ * @param progressMessage the message printed during the progression of the build
+ * @param mnemonic the mnemonic that is reported in the master log.
+ */
+ public SpawnAction(ActionOwner owner,
+ Iterable<Artifact> inputs, Iterable<Artifact> outputs,
+ ResourceSet resourceSet,
+ CommandLine argv,
+ Map<String, String> environment,
+ String progressMessage,
+ String mnemonic) {
+ this(owner, inputs, outputs,
+ resourceSet, argv, ImmutableMap.copyOf(environment),
+ ImmutableMap.<String, String>of(), progressMessage,
+ ImmutableMap.<PathFragment, Artifact>of(), mnemonic, null);
+ }
+
+ /**
+ * Constructs a SpawnAction using direct initialization arguments.
+ *
+ * <p>All collections provided must not be subsequently modified.
+ *
+ * @param owner the owner of the Action.
+ * @param inputs the set of all files potentially read by this action; must
+ * not be subsequently modified.
+ * @param outputs the set of all files written by this action; must not be
+ * subsequently modified.
+ * @param resourceSet the resources consumed by executing this Action
+ * @param environment the map of environment variables.
+ * @param executionInfo out-of-band information for scheduling the spawn.
+ * @param argv the argv array (including argv[0]) of arguments to pass. This
+ * is merely a list of options to the executable, and is uninterpreted
+ * by the build tool for the purposes of dependency checking; typically
+ * it may include the names of input and output files, but this is not
+ * necessary.
+ * @param progressMessage the message printed during the progression of the build
+ * @param inputManifests entries in inputs that are symlink manifest files.
+ * These are passed to remote execution in the environment rather than as inputs.
+ * @param mnemonic the mnemonic that is reported in the master log.
+ */
+ public SpawnAction(ActionOwner owner,
+ Iterable<Artifact> inputs, Iterable<Artifact> outputs,
+ ResourceSet resourceSet,
+ CommandLine argv,
+ ImmutableMap<String, String> environment,
+ ImmutableMap<String, String> executionInfo,
+ String progressMessage,
+ ImmutableMap<PathFragment, Artifact> inputManifests,
+ String mnemonic,
+ ExtraActionInfoSupplier<?> extraActionInfoSupplier) {
+ super(owner, inputs, outputs);
+ this.resourceSet = resourceSet;
+ this.executionInfo = executionInfo;
+ this.environment = environment;
+ this.argv = argv;
+ this.progressMessage = progressMessage;
+ this.inputManifests = inputManifests;
+ this.mnemonic = mnemonic;
+ this.extraActionInfoSupplier = extraActionInfoSupplier;
+ }
+
+ /**
+ * Returns the (immutable) list of all arguments, including the command name, argv[0].
+ */
+ @VisibleForTesting
+ public List<String> getArguments() {
+ return ImmutableList.copyOf(argv.arguments());
+ }
+
+ /**
+ * Returns command argument, argv[0].
+ */
+ @VisibleForTesting
+ public String getCommandFilename() {
+ return Iterables.getFirst(argv.arguments(), null);
+ }
+
+ /**
+ * Returns the (immutable) list of arguments, excluding the command name,
+ * argv[0].
+ */
+ @VisibleForTesting
+ public List<String> getRemainingArguments() {
+ return ImmutableList.copyOf(Iterables.skip(argv.arguments(), 1));
+ }
+
+ @VisibleForTesting
+ public boolean isShellCommand() {
+ return argv.isShellCommand();
+ }
+
+ /**
+ * Executes the action without handling ExecException errors.
+ *
+ * <p>Called by {@link #execute}.
+ */
+ protected void internalExecute(
+ ActionExecutionContext actionExecutionContext) throws ExecException, InterruptedException {
+ getContext(actionExecutionContext.getExecutor()).exec(getSpawn(), actionExecutionContext);
+ }
+
+ @Override
+ public void execute(ActionExecutionContext actionExecutionContext)
+ throws ActionExecutionException, InterruptedException {
+ Executor executor = actionExecutionContext.getExecutor();
+ try {
+ internalExecute(actionExecutionContext);
+ } catch (ExecException e) {
+ String failMessage = progressMessage;
+ if (isShellCommand()) {
+ // The possible reasons it could fail are: shell executable not found, shell
+ // exited non-zero, or shell died from signal. The first is impossible
+ // and the second two aren't very interesting, so in the interests of
+ // keeping the noise-level down, we don't print a reason why, just the
+ // command that failed.
+ //
+ // 0=shell executable, 1=shell command switch, 2=command
+ failMessage = "error executing shell command: " + "'"
+ + truncate(Iterables.get(argv.arguments(), 2), 200) + "'";
+ }
+ throw e.toActionExecutionException(failMessage, executor.getVerboseFailures(), this);
+ }
+ }
+
+ /**
+ * Returns s, truncated to no more than maxLen characters, appending an
+ * ellipsis if truncation occurred.
+ */
+ private static String truncate(String s, int maxLen) {
+ return s.length() > maxLen
+ ? s.substring(0, maxLen - "...".length()) + "..."
+ : s;
+ }
+
+ /**
+ * Returns a Spawn that is representative of the command that this Action
+ * will execute. This function must not modify any state.
+ */
+ public Spawn getSpawn() {
+ return new ActionSpawn();
+ }
+
+ @Override
+ protected String computeKey() {
+ Fingerprint f = new Fingerprint();
+ f.addString(GUID);
+ f.addStrings(argv.arguments());
+ f.addString(getMnemonic());
+ f.addInt(inputManifests.size());
+ for (Map.Entry<PathFragment, Artifact> input : inputManifests.entrySet()) {
+ f.addString(input.getKey().getPathString() + "/");
+ f.addPath(input.getValue().getExecPath());
+ }
+ f.addStringMap(getEnvironment());
+ f.addStringMap(getExecutionInfo());
+ return f.hexDigestAndReset();
+ }
+
+ @Override
+ public String describeKey() {
+ StringBuilder message = new StringBuilder();
+ message.append(getProgressMessage());
+ message.append('\n');
+ for (Map.Entry<String, String> entry : getEnvironment().entrySet()) {
+ message.append(" Environment variable: ");
+ message.append(ShellEscaper.escapeString(entry.getKey()));
+ message.append('=');
+ message.append(ShellEscaper.escapeString(entry.getValue()));
+ message.append('\n');
+ }
+ for (String argument : ShellEscaper.escapeAll(argv.arguments())) {
+ message.append(" Argument: ");
+ message.append(argument);
+ message.append('\n');
+ }
+ return message.toString();
+ }
+
+ @Override
+ public final String getMnemonic() {
+ return mnemonic;
+ }
+
+ @Override
+ protected String getRawProgressMessage() {
+ return progressMessage;
+ }
+
+ @Override
+ public ExtraActionInfo.Builder getExtraActionInfo() {
+ ExtraActionInfo.Builder builder = super.getExtraActionInfo();
+ if (extraActionInfoSupplier == null) {
+ Spawn spawn = getSpawn();
+ SpawnInfo spawnInfo = spawn.getExtraActionInfo();
+
+ return builder
+ .setExtension(SpawnInfo.spawnInfo, spawnInfo);
+ } else {
+ extraActionInfoSupplier.extend(builder);
+ return builder;
+ }
+ }
+
+ /**
+ * Returns the environment in which to run this action.
+ */
+ public Map<String, String> getEnvironment() {
+ return environment;
+ }
+
+ /**
+ * Returns the out-of-band execution data for this action.
+ */
+ public Map<String, String> getExecutionInfo() {
+ return executionInfo;
+ }
+
+ @Override
+ public String describeStrategy(Executor executor) {
+ return getContext(executor).strategyLocality(getMnemonic(), isRemotable());
+ }
+
+ protected SpawnActionContext getContext(Executor executor) {
+ return executor.getSpawnActionContext(getMnemonic());
+ }
+
+ @Override
+ public ResourceSet estimateResourceConsumption(Executor executor) {
+ SpawnActionContext context = getContext(executor);
+ if (context.isRemotable(getMnemonic(), isRemotable())) {
+ return ResourceSet.ZERO;
+ }
+ return resourceSet;
+ }
+
+ /**
+ * Returns true if this can be run remotely.
+ */
+ public final boolean isRemotable() {
+ // TODO(bazel-team): get rid of this method.
+ return !executionInfo.containsKey("local");
+ }
+
+ /**
+ * The Spawn which this SpawnAction will execute.
+ */
+ private class ActionSpawn extends BaseSpawn {
+
+ private final List<Artifact> filesets = new ArrayList<>();
+
+ public ActionSpawn() {
+ super(ImmutableList.copyOf(argv.arguments()),
+ ImmutableMap.<String, String>of(),
+ executionInfo,
+ inputManifests,
+ SpawnAction.this,
+ resourceSet);
+ for (Artifact input : getInputs()) {
+ if (input.isFileset()) {
+ filesets.add(input);
+ }
+ }
+ }
+
+ @Override
+ public ImmutableMap<String, String> getEnvironment() {
+ return ImmutableMap.copyOf(SpawnAction.this.getEnvironment());
+ }
+
+ @Override
+ public ImmutableList<Artifact> getFilesetManifests() {
+ return ImmutableList.copyOf(filesets);
+ }
+
+ @Override
+ public Iterable<? extends ActionInput> getInputFiles() {
+ // Remove Fileset directories in inputs list. Instead, these are
+ // included as manifests in getEnvironment().
+ List<Artifact> inputs = Lists.newArrayList(getInputs());
+ inputs.removeAll(filesets);
+ inputs.removeAll(inputManifests.values());
+ return inputs;
+ // Also expand middleman artifacts.
+ }
+ }
+
+ /**
+ * Builder class to construct {@link SpawnAction} instances.
+ */
+ public static class Builder {
+
+ private final NestedSetBuilder<Artifact> inputsBuilder =
+ NestedSetBuilder.stableOrder();
+ private final List<Artifact> outputs = new ArrayList<>();
+ private final Map<PathFragment, Artifact> inputManifests = new LinkedHashMap<>();
+ private ResourceSet resourceSet = AbstractAction.DEFAULT_RESOURCE_SET;
+ private ImmutableMap<String, String> environment = ImmutableMap.of();
+ private ImmutableMap<String, String> executionInfo = ImmutableMap.of();
+ private boolean isShellCommand = false;
+ private boolean useDefaultShellEnvironment = false;
+ private PathFragment executable;
+ // executableArgs does not include the executable itself.
+ private List<String> executableArgs;
+ private final IterablesChain.Builder<String> argumentsBuilder = IterablesChain.builder();
+ private CommandLine commandLine;
+
+ private String progressMessage;
+ private ParamFileInfo paramFileInfo = null;
+ private String mnemonic = "Unknown";
+ private ExtraActionInfoSupplier<?> extraActionInfoSupplier = null;
+
+ /**
+ * Creates a SpawnAction builder.
+ */
+ public Builder() {}
+
+ /**
+ * Creates a builder that is a copy of another builder.
+ */
+ public Builder(Builder other) {
+ this.inputsBuilder.addTransitive(other.inputsBuilder.build());
+ this.outputs.addAll(other.outputs);
+ this.inputManifests.putAll(other.inputManifests);
+ this.resourceSet = other.resourceSet;
+ this.environment = other.environment;
+ this.executionInfo = other.executionInfo;
+ this.isShellCommand = other.isShellCommand;
+ this.useDefaultShellEnvironment = other.useDefaultShellEnvironment;
+ this.executable = other.executable;
+ this.executableArgs = (other.executableArgs != null)
+ ? Lists.newArrayList(other.executableArgs)
+ : null;
+ this.argumentsBuilder.add(other.argumentsBuilder.build());
+ this.commandLine = other.commandLine;
+ this.progressMessage = other.progressMessage;
+ this.paramFileInfo = other.paramFileInfo;
+ this.mnemonic = other.mnemonic;
+ }
+
+ /**
+ * Builds the SpawnAction using the passed in action configuration and returns it and all
+ * dependent actions. The first item of the returned array is always the SpawnAction itself.
+ *
+ * <p>This method makes a copy of all the collections, so it is safe to reuse the builder after
+ * this method returns.
+ *
+ * <p>This is annotated with @CheckReturnValue, which causes a compiler error when you call this
+ * method and ignore its return value. This is because some time ago, calling .build() had the
+ * side-effect of registering it with the RuleContext that was passed in to the constructor.
+ * This logic was removed, but if people don't notice and still rely on the side-effect, things
+ * may break.
+ *
+ * @return the SpawnAction and any actions required by it, with the first item always being the
+ * SpawnAction itself.
+ */
+ @CheckReturnValue
+ public Action[] build(ActionConstructionContext context) {
+ return build(context.getActionOwner(), context.getAnalysisEnvironment(),
+ context.getConfiguration());
+ }
+
+ @VisibleForTesting @CheckReturnValue
+ public Action[] build(ActionOwner owner, AnalysisEnvironment analysisEnvironment,
+ BuildConfiguration configuration) {
+ if (isShellCommand && executable == null) {
+ executable = configuration.getShExecutable();
+ }
+ Preconditions.checkNotNull(executable);
+ Preconditions.checkNotNull(executableArgs);
+
+ if (useDefaultShellEnvironment) {
+ this.environment = configuration.getDefaultShellEnvironment();
+ }
+
+ ImmutableList<String> argv = ImmutableList.<String>builder()
+ .add(executable.getPathString())
+ .addAll(executableArgs)
+ .build();
+
+ Iterable<String> arguments = argumentsBuilder.build();
+
+ Artifact paramsFile = ParamFileHelper.getParamsFile(argv, arguments, commandLine,
+ paramFileInfo, configuration, analysisEnvironment, outputs);
+
+ List<Action> actions = new ArrayList<>();
+ CommandLine actualCommandLine;
+ if (paramsFile != null) {
+ actualCommandLine = ParamFileHelper.createWithParamsFile(argv, arguments, commandLine,
+ isShellCommand, owner, actions, paramFileInfo, paramsFile);
+ } else {
+ actualCommandLine = ParamFileHelper.createWithoutParamsFile(argv, arguments, commandLine,
+ isShellCommand);
+ }
+
+ Iterable<Artifact> actualInputs = collectActualInputs(paramsFile);
+
+ actions.add(0, new SpawnAction(owner, actualInputs, ImmutableList.copyOf(outputs),
+ resourceSet, actualCommandLine, environment, executionInfo, progressMessage,
+ ImmutableMap.copyOf(inputManifests), mnemonic, extraActionInfoSupplier));
+ return actions.toArray(new Action[actions.size()]);
+ }
+
+ private Iterable<Artifact> collectActualInputs(Artifact parameterFile) {
+ if (parameterFile != null) {
+ inputsBuilder.add(parameterFile);
+ }
+ return inputsBuilder.build();
+ }
+
+ /**
+ * Adds an input to this action.
+ */
+ public Builder addInput(Artifact artifact) {
+ inputsBuilder.add(artifact);
+ return this;
+ }
+
+ /**
+ * Adds inputs to this action.
+ */
+ public Builder addInputs(Iterable<Artifact> artifacts) {
+ inputsBuilder.addAll(artifacts);
+ return this;
+ }
+
+ /**
+ * Adds transitive inputs to this action.
+ */
+ public Builder addTransitiveInputs(NestedSet<Artifact> artifacts) {
+ inputsBuilder.addTransitive(artifacts);
+ return this;
+ }
+
+ public Builder addInputManifest(Artifact artifact, PathFragment remote) {
+ inputManifests.put(remote, artifact);
+ return this;
+ }
+
+ public Builder addOutput(Artifact artifact) {
+ outputs.add(artifact);
+ return this;
+ }
+
+ public Builder addOutputs(Iterable<Artifact> artifacts) {
+ Iterables.addAll(outputs, artifacts);
+ return this;
+ }
+
+ public Builder setResources(ResourceSet resourceSet) {
+ this.resourceSet = resourceSet;
+ return this;
+ }
+
+ /**
+ * Sets the map of environment variables.
+ */
+ public Builder setEnvironment(Map<String, String> environment) {
+ this.environment = ImmutableMap.copyOf(environment);
+ this.useDefaultShellEnvironment = false;
+ return this;
+ }
+
+ /**
+ * Sets the map of execution info.
+ */
+ public Builder setExecutionInfo(Map<String, String> info) {
+ this.executionInfo = ImmutableMap.copyOf(info);
+ return this;
+ }
+
+ /**
+ * Sets the environment to the configurations default shell environment,
+ * see {@link BuildConfiguration#getDefaultShellEnvironment}.
+ */
+ public Builder useDefaultShellEnvironment() {
+ this.environment = null;
+ this.useDefaultShellEnvironment = true;
+ return this;
+ }
+
+ /**
+ * Sets the executable path; the path is interpreted relative to the
+ * execution root.
+ *
+ * <p>Calling this method overrides any previous values set via calls to
+ * {@link #setExecutable(Artifact)}, {@link #setJavaExecutable}, or
+ * {@link #setShellCommand(String)}.
+ */
+ public Builder setExecutable(PathFragment executable) {
+ this.executable = executable;
+ this.executableArgs = Lists.newArrayList();
+ this.isShellCommand = false;
+ return this;
+ }
+
+ /**
+ * Sets the executable as an artifact.
+ *
+ * <p>Calling this method overrides any previous values set via calls to
+ * {@link #setExecutable(Artifact)}, {@link #setJavaExecutable}, or
+ * {@link #setShellCommand(String)}.
+ */
+ public Builder setExecutable(Artifact executable) {
+ return setExecutable(executable.getExecPath());
+ }
+
+ /**
+ * Sets the executable as a configured target. Automatically adds the files
+ * to run to the inputs and uses the executable of the target as the
+ * executable.
+ *
+ * <p>Calling this method overrides any previous values set via calls to
+ * {@link #setExecutable(Artifact)}, {@link #setJavaExecutable}, or
+ * {@link #setShellCommand(String)}.
+ */
+ public Builder setExecutable(TransitiveInfoCollection executable) {
+ FilesToRunProvider provider = executable.getProvider(FilesToRunProvider.class);
+ Preconditions.checkArgument(provider != null);
+ return setExecutable(provider);
+ }
+
+ /**
+ * Sets the executable as a configured target. Automatically adds the files
+ * to run to the inputs and uses the executable of the target as the
+ * executable.
+ *
+ * <p>Calling this method overrides any previous values set via calls to
+ * {@link #setExecutable}, {@link #setJavaExecutable}, or
+ * {@link #setShellCommand(String)}.
+ */
+ public Builder setExecutable(FilesToRunProvider executableProvider) {
+ Preconditions.checkArgument(executableProvider.getExecutable() != null,
+ "The target does not have an executable");
+ setExecutable(executableProvider.getExecutable().getExecPath());
+ return addTool(executableProvider);
+ }
+
+ private Builder setJavaExecutable(PathFragment javaExecutable, Artifact deployJar,
+ List<String> jvmArgs, String... launchArgs) {
+ this.executable = javaExecutable;
+ this.executableArgs = Lists.newArrayList();
+ executableArgs.add("-Xverify:none");
+ executableArgs.addAll(jvmArgs);
+ for (String arg : launchArgs) {
+ executableArgs.add(arg);
+ }
+ inputsBuilder.add(deployJar);
+ this.isShellCommand = false;
+ return this;
+ }
+
+ /**
+ * Sets the executable to be a java class executed from the given deploy
+ * jar. The deploy jar is automatically added to the action inputs.
+ *
+ * <p>Calling this method overrides any previous values set via calls to
+ * {@link #setExecutable}, {@link #setJavaExecutable}, or
+ * {@link #setShellCommand(String)}.
+ */
+ public Builder setJavaExecutable(PathFragment javaExecutable,
+ Artifact deployJar, String javaMainClass, List<String> jvmArgs) {
+ return setJavaExecutable(javaExecutable, deployJar, jvmArgs, "-cp",
+ deployJar.getExecPathString(), javaMainClass);
+ }
+
+ /**
+ * Sets the executable to be a jar executed from the given deploy jar. The deploy jar is
+ * automatically added to the action inputs.
+ *
+ * <p>This method is similar to {@link #setJavaExecutable} but it assumes that the Jar artifact
+ * declares a main class.
+ *
+ * <p>Calling this method overrides any previous values set via calls to {@link #setExecutable},
+ * {@link #setJavaExecutable}, or {@link #setShellCommand(String)}.
+ */
+ public Builder setJarExecutable(PathFragment javaExecutable,
+ Artifact deployJar, List<String> jvmArgs) {
+ return setJavaExecutable(javaExecutable, deployJar, jvmArgs, "-jar",
+ deployJar.getExecPathString());
+ }
+
+ /**
+ * Sets the executable to be the shell and adds the given command as the
+ * command to be executed.
+ *
+ * <p>Note that this will not clear the arguments, so any arguments will
+ * be passed in addition to the command given here.
+ *
+ * <p>Calling this method overrides any previous values set via calls to
+ * {@link #setExecutable(Artifact)}, {@link #setJavaExecutable}, or
+ * {@link #setShellCommand(String)}.
+ */
+ public Builder setShellCommand(String command) {
+ this.executable = null;
+ // 0=shell command switch, 1=command
+ this.executableArgs = Lists.newArrayList("-c", command);
+ this.isShellCommand = true;
+ return this;
+ }
+
+ /**
+ * Sets the executable to be the shell and adds the given interned commands as the
+ * commands to be executed.
+ */
+ public Builder setShellCommand(Iterable<String> command) {
+ this.executable = new PathFragment(Iterables.getFirst(command, null));
+ // The first item of the commands is the shell executable that should be used.
+ this.executableArgs = ImmutableList.copyOf(Iterables.skip(command, 1));
+ this.isShellCommand = true;
+ return this;
+ }
+
+ /**
+ * Adds an executable and its runfiles, so it can be called from a shell command.
+ */
+ public Builder addTool(FilesToRunProvider tool) {
+ addInputs(tool.getFilesToRun());
+ if (tool.getRunfilesManifest() != null) {
+ addInputManifest(tool.getRunfilesManifest(),
+ BaseSpawn.runfilesForFragment(tool.getExecutable().getExecPath()));
+ }
+ return this;
+ }
+
+ /**
+ * Appends the arguments to the list of executable arguments.
+ */
+ public Builder addExecutableArguments(String... arguments) {
+ Preconditions.checkState(executableArgs != null);
+ executableArgs.addAll(Arrays.asList(arguments));
+ return this;
+ }
+
+ /**
+ * Add multiple arguments in the order they are returned by the collection
+ * to the list of executable arguments.
+ */
+ public Builder addExecutableArguments(Iterable<String> arguments) {
+ Preconditions.checkState(executableArgs != null);
+ Iterables.addAll(executableArgs, arguments);
+ return this;
+ }
+
+ /**
+ * Appends the argument to the list of command-line arguments.
+ */
+ public Builder addArgument(String argument) {
+ Preconditions.checkState(commandLine == null);
+ argumentsBuilder.addElement(argument);
+ return this;
+ }
+
+ /**
+ * Appends the arguments to the list of command-line arguments.
+ */
+ public Builder addArguments(String... arguments) {
+ Preconditions.checkState(commandLine == null);
+ argumentsBuilder.add(ImmutableList.copyOf(arguments));
+ return this;
+ }
+
+ /**
+ * Add multiple arguments in the order they are returned by the collection.
+ */
+ public Builder addArguments(Iterable<String> arguments) {
+ Preconditions.checkState(commandLine == null);
+ argumentsBuilder.add(CollectionUtils.makeImmutable(arguments));
+ return this;
+ }
+
+ /**
+ * Appends the argument both to the inputs and to the list of command-line
+ * arguments.
+ */
+ public Builder addInputArgument(Artifact argument) {
+ Preconditions.checkState(commandLine == null);
+ addInput(argument);
+ addArgument(argument.getExecPathString());
+ return this;
+ }
+
+ /**
+ * Appends the arguments both to the inputs and to the list of command-line
+ * arguments.
+ */
+ public Builder addInputArguments(Iterable<Artifact> arguments) {
+ for (Artifact argument : arguments) {
+ addInputArgument(argument);
+ }
+ return this;
+ }
+
+ /**
+ * Appends the argument both to the ouputs and to the list of command-line
+ * arguments.
+ */
+ public Builder addOutputArgument(Artifact argument) {
+ Preconditions.checkState(commandLine == null);
+ outputs.add(argument);
+ argumentsBuilder.addElement(argument.getExecPathString());
+ return this;
+ }
+
+ /**
+ * Sets a delegate to compute the command line at a later time. This method
+ * cannot be used in conjunction with the {@link #addArgument} or {@link
+ * #addArguments} methods.
+ *
+ * <p>The main intention of this method is to save memory by allowing
+ * client-controlled sharing between actions and configured targets.
+ * Objects passed to this method MUST be immutable.
+ */
+ public Builder setCommandLine(CommandLine commandLine) {
+ Preconditions.checkState(argumentsBuilder.isEmpty());
+ this.commandLine = commandLine;
+ return this;
+ }
+
+ public Builder setProgressMessage(String progressMessage) {
+ this.progressMessage = progressMessage;
+ return this;
+ }
+
+ public Builder setMnemonic(String mnemonic) {
+ Preconditions.checkArgument(
+ !mnemonic.isEmpty() && CharMatcher.JAVA_LETTER_OR_DIGIT.matchesAllOf(mnemonic),
+ "mnemonic must only contain letters and/or digits, and have non-zero length, was: \"%s\"",
+ mnemonic);
+ this.mnemonic = mnemonic;
+ return this;
+ }
+
+ public <T> Builder setExtraActionInfo(
+ GeneratedExtension<ExtraActionInfo, T> extension, T value) {
+ this.extraActionInfoSupplier = new ExtraActionInfoSupplier<T>(extension, value);
+ return this;
+ }
+
+ /**
+ * Enable use of a parameter file and set the encoding to ISO-8859-1 (latin1).
+ *
+ * <p>In order to use parameter files, at least one output artifact must be specified.
+ */
+ public Builder useParameterFile(ParameterFileType parameterFileType) {
+ return useParameterFile(parameterFileType, ISO_8859_1, "@");
+ }
+
+ /**
+ * Enable or disable the use of a parameter file, set the encoding to the given value, and
+ * specify the argument prefix to use in passing the parameter file name to the tool.
+ *
+ * <p>The default argument prefix is "@". In order to use parameter files, at least one output
+ * artifact must be specified.
+ */
+ public Builder useParameterFile(
+ ParameterFileType parameterFileType, Charset charset, String flagPrefix) {
+ paramFileInfo = new ParamFileInfo(parameterFileType, charset, flagPrefix);
+ return this;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/SymlinkAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/SymlinkAction.java
new file mode 100644
index 0000000..2bbb3e2
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/SymlinkAction.java
@@ -0,0 +1,130 @@
+// 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.actions;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.AbstractAction;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.io.IOException;
+
+/**
+ * Action to create a symbolic link.
+ */
+public class SymlinkAction extends AbstractAction {
+
+ private static final String GUID = "349675b5-437c-4da8-891a-7fb98fba6ab5";
+
+ private final PathFragment inputPath;
+ private final Artifact output;
+ private final String progressMessage;
+
+ /**
+ * Creates a new SymlinkAction instance.
+ *
+ * @param owner the action owner.
+ * @param input the Artifact that will be the src of the symbolic link.
+ * @param output the Artifact that will be created by executing this Action.
+ * @param progressMessage the progress message.
+ */
+ public SymlinkAction(ActionOwner owner, Artifact input, Artifact output,
+ String progressMessage) {
+ // These actions typically have only one input and one output, which
+ // become the sole and primary in their respective lists.
+ this(owner, input.getExecPath(), input, output, progressMessage);
+ }
+
+ /**
+ * Creates a new SymlinkAction instance, where the inputPath
+ * may be different than that input artifact's path. This is
+ * only useful when dealing with runfiles trees where
+ * link target is a directory.
+ *
+ * @param owner the action owner.
+ * @param inputPath the Path that will be the src of the symbolic link.
+ * @param input the Artifact that is required to build the inputPath.
+ * @param output the Artifact that will be created by executing this Action.
+ * @param progressMessage the progress message.
+ */
+ public SymlinkAction(ActionOwner owner, PathFragment inputPath, Artifact input,
+ Artifact output, String progressMessage) {
+ super(owner, ImmutableList.of(input), ImmutableList.of(output));
+ this.inputPath = Preconditions.checkNotNull(inputPath);
+ this.output = Preconditions.checkNotNull(output);
+ this.progressMessage = progressMessage;
+ }
+
+ public PathFragment getInputPath() {
+ return inputPath;
+ }
+
+ public Path getOutputPath() {
+ return output.getPath();
+ }
+
+ @Override
+ public void execute(ActionExecutionContext actionExecutionContext)
+ throws ActionExecutionException {
+ try {
+ getOutputPath().createSymbolicLink(
+ actionExecutionContext.getExecutor().getExecRoot().getRelative(inputPath));
+ } catch (IOException e) {
+ throw new ActionExecutionException("failed to create symbolic link '"
+ + Iterables.getOnlyElement(getOutputs()).prettyPrint()
+ + "' to the '" + Iterables.getOnlyElement(getInputs()).prettyPrint()
+ + "' due to I/O error: " + e.getMessage(), e, this, false);
+ }
+ }
+
+ @Override
+ public ResourceSet estimateResourceConsumption(Executor executor) {
+ return new ResourceSet(/*memoryMb=*/0, /*cpuUsage=*/0, /*ioUsage=*/0.0);
+ }
+
+ @Override
+ protected String computeKey() {
+ Fingerprint f = new Fingerprint();
+ f.addString(GUID);
+ // We don't normally need to add inputs to the key. In this case, however, the inputPath can be
+ // different from the actual input artifact.
+ f.addPath(inputPath);
+ return f.hexDigestAndReset();
+ }
+
+ @Override
+ public String getMnemonic() {
+ return "Symlink";
+ }
+
+ @Override
+ protected String getRawProgressMessage() {
+ return progressMessage;
+ }
+
+ @Override
+ public String describeStrategy(Executor executor) {
+ return "local";
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionAction.java
new file mode 100644
index 0000000..b2c83fb
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/TemplateExpansionAction.java
@@ -0,0 +1,335 @@
+// 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.actions;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.util.ResourceFileLoader;
+import com.google.devtools.build.lib.util.StringUtilities;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Action to expand a template and write the expanded content to a file.
+ */
+public class TemplateExpansionAction extends AbstractFileWriteAction {
+
+ private static final String GUID = "786c1fe0-dca8-407a-b108-e1ecd6d1bc7f";
+
+ /**
+ * A pair of a string to be substituted and a string to substitute it with.
+ * For simplicity, these are called key and value. All implementations must
+ * be immutable, and always return the identical key. The returned values
+ * must be the same, though they need not be the same object.
+ *
+ * <p>It should be assumed that the {@link #getKey} invocation is cheap, and
+ * that the {@link #getValue} invocation is expensive.
+ */
+ public abstract static class Substitution {
+ private Substitution() {
+ }
+
+ public abstract String getKey();
+ public abstract String getValue();
+
+ /**
+ * Returns an immutable Substitution instance for the given key and value.
+ */
+ public static Substitution of(final String key, final String value) {
+ return new Substitution() {
+ @Override
+ public String getKey() {
+ return key;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+ };
+ }
+
+ /**
+ * Returns an immutable Substitution instance for the key and list of values. The
+ * values will be joined by spaces before substitution.
+ */
+ public static Substitution ofSpaceSeparatedList(final String key, final List<?> value) {
+ return new Substitution() {
+ @Override
+ public String getKey() {
+ return key;
+ }
+
+ @Override
+ public String getValue() {
+ return Joiner.on(" ").join(value);
+ }
+ };
+ }
+ }
+
+ /**
+ * A substitution with a fixed key, and a computed value. The computed value
+ * must not change over the lifetime of an instance, though the {@link
+ * #getValue} method may return different String objects.
+ *
+ * <p>It should be assumed that the {@link #getKey} invocation is cheap, and
+ * that the {@link #getValue} invocation is expensive.
+ */
+ public abstract static class ComputedSubstitution extends Substitution {
+ private final String key;
+
+ public ComputedSubstitution(String key) {
+ this.key = key;
+ }
+
+ @Override
+ public String getKey() {
+ return key;
+ }
+ }
+
+ /**
+ * A template that contains text content, or alternatively throws an {@link
+ * IOException}.
+ */
+ public abstract static class Template {
+
+ /**
+ * We only allow subclasses in this file.
+ */
+ private Template() {
+ }
+
+ /**
+ * Returns the text content of the template.
+ */
+ protected abstract String getContent() throws IOException;
+
+ /**
+ * Returns a string that is used for the action key. This must change if
+ * the getContent method returns something different, but is not allowed to
+ * throw an exception.
+ */
+ protected abstract String getKey();
+
+ /**
+ * Loads a template from the given resource. The resource is looked up
+ * relative to the given class. If the resource cannot be loaded, the returned
+ * template throws an {@link IOException} when {@link #getContent} is
+ * called. This makes it safe to use this method in a constant initializer.
+ */
+ public static Template forResource(final Class<?> relativeToClass, final String templateName) {
+ try {
+ String content = ResourceFileLoader.loadResource(relativeToClass, templateName);
+ return forString(content);
+ } catch (final IOException e) {
+ return new Template() {
+ @Override
+ protected String getContent() throws IOException {
+ throw new IOException("failed to load resource file '" + templateName
+ + "' due to I/O error: " + e.getMessage(), e);
+ }
+
+ @Override
+ protected String getKey() {
+ return "ERROR: " + e.getMessage();
+ }
+ };
+ }
+ }
+
+ /**
+ * Returns a template for the given text string.
+ */
+ public static Template forString(final String templateText) {
+ return new Template() {
+ @Override
+ protected String getContent() {
+ return templateText;
+ }
+
+ @Override
+ protected String getKey() {
+ return templateText;
+ }
+ };
+ }
+
+ /**
+ * Returns a template that loads the given artifact. It is important that
+ * the artifact is also an input for the action, or this won't work.
+ * Therefore this method is private, and you should use the corresponding
+ * {@link TemplateExpansionAction} constructor.
+ */
+ private static Template forArtifact(final Artifact templateArtifact) {
+ return new Template() {
+ @Override
+ protected String getContent() throws IOException {
+ Path templatePath = templateArtifact.getPath();
+ try {
+ return new String(FileSystemUtils.readContentAsLatin1(templatePath));
+ } catch (IOException e) {
+ throw new IOException("failed to load template file '" + templatePath.getPathString()
+ + "' due to I/O error: " + e.getMessage(), e);
+ }
+ }
+
+ @Override
+ protected String getKey() {
+ // This isn't strictly necessary, because the action inputs are automatically considered.
+ return "ARTIFACT: " + templateArtifact.getExecPathString();
+ }
+ };
+ }
+ }
+
+ private final Template template;
+ private final List<Substitution> substitutions;
+
+ /**
+ * Creates a new TemplateExpansionAction instance.
+ *
+ * @param owner the action owner.
+ * @param inputs the Artifacts that this Action depends on
+ * @param output the Artifact that will be created by executing this Action.
+ * @param template the template that will be expanded by this Action.
+ * @param substitutions the substitutions that will be applied to the
+ * template. All substitutions will be applied in order.
+ * @param makeExecutable iff true will change the output file to be
+ * executable.
+ */
+ private TemplateExpansionAction(ActionOwner owner,
+ Collection<Artifact> inputs,
+ Artifact output,
+ Template template,
+ List<Substitution> substitutions,
+ boolean makeExecutable) {
+ super(owner, inputs, output, makeExecutable);
+ this.template = template;
+ this.substitutions = ImmutableList.copyOf(substitutions);
+ }
+
+ /**
+ * Creates a new TemplateExpansionAction instance for an artifact template.
+ *
+ * @param owner the action owner.
+ * @param templateArtifact the Artifact that will be read as the text template
+ * file
+ * @param output the Artifact that will be created by executing this Action.
+ * @param substitutions the substitutions that will be applied to the
+ * template. All substitutions will be applied in order.
+ * @param makeExecutable iff true will change the output file to be
+ * executable.
+ */
+ public TemplateExpansionAction(ActionOwner owner,
+ Artifact templateArtifact,
+ Artifact output,
+ List<Substitution> substitutions,
+ boolean makeExecutable) {
+ this(owner, ImmutableList.of(templateArtifact), output, Template.forArtifact(templateArtifact),
+ substitutions, makeExecutable);
+ }
+
+ /**
+ * Creates a new TemplateExpansionAction instance without inputs.
+ *
+ * @param owner the action owner.
+ * @param output the Artifact that will be created by executing this Action.
+ * @param template the template
+ * @param substitutions the substitutions that will be applied to the
+ * template. All substitutions will be applied in order.
+ * @param makeExecutable iff true will change the output file to be
+ * executable.
+ */
+ public TemplateExpansionAction(ActionOwner owner,
+ Artifact output,
+ Template template,
+ List<Substitution> substitutions,
+ boolean makeExecutable) {
+ this(owner, Artifact.NO_ARTIFACTS, output, template, substitutions, makeExecutable);
+ }
+
+ /**
+ * Expands the template by applying all substitutions.
+ * @param template
+ * @return the expanded text.
+ */
+ private String expandTemplate(String template) {
+ for (Substitution entry : substitutions) {
+ template = StringUtilities.replaceAllLiteral(template, entry.getKey(), entry.getValue());
+ }
+ return template;
+ }
+
+ @VisibleForTesting
+ public String getFileContents() throws IOException {
+ return expandTemplate(template.getContent());
+ }
+
+ @Override
+ public DeterministicWriter newDeterministicWriter(EventHandler eventHandler,
+ Executor executor) throws IOException {
+ final byte[] bytes = getFileContents().getBytes(UTF_8);
+ return new DeterministicWriter() {
+ @Override
+ public void writeOutputFile(OutputStream out) throws IOException {
+ out.write(bytes);
+ }
+ };
+ }
+
+ @Override
+ protected String computeKey() {
+ Fingerprint f = new Fingerprint();
+ f.addString(GUID);
+ f.addString(String.valueOf(makeExecutable));
+ f.addString(template.getKey());
+ f.addInt(substitutions.size());
+ for (Substitution entry : substitutions) {
+ f.addString(entry.getKey());
+ f.addString(entry.getValue());
+ }
+ return f.hexDigestAndReset();
+ }
+
+ @Override
+ public String getMnemonic() {
+ return "TemplateExpand";
+ }
+
+ @Override
+ protected String getRawProgressMessage() {
+ return "Expanding template " + Iterables.getOnlyElement(getOutputs()).prettyPrint();
+ }
+
+ public List<Substitution> getSubstitutions() {
+ return substitutions;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/buildinfo/BuildInfoCollection.java b/src/main/java/com/google/devtools/build/lib/analysis/buildinfo/BuildInfoCollection.java
new file mode 100644
index 0000000..54067a0
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/buildinfo/BuildInfoCollection.java
@@ -0,0 +1,48 @@
+// 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.buildinfo;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Action;
+import com.google.devtools.build.lib.actions.Artifact;
+
+import java.util.List;
+
+/**
+ * A collection of build-info files for both stamped and unstamped modes.
+ */
+public final class BuildInfoCollection {
+ private final ImmutableList<Action> actions;
+ private final ImmutableList<Artifact> stampedBuildInfo;
+ private final ImmutableList<Artifact> redactedBuildInfo;
+
+ public BuildInfoCollection(List<? extends Action> actions, List<Artifact> stampedBuildInfo,
+ List<Artifact> redactedBuildInfo) {
+ this.actions = ImmutableList.copyOf(actions);
+ this.stampedBuildInfo = ImmutableList.copyOf(stampedBuildInfo);
+ this.redactedBuildInfo = ImmutableList.copyOf(redactedBuildInfo);
+ }
+
+ public ImmutableList<Action> getActions() {
+ return actions;
+ }
+
+ public ImmutableList<Artifact> getStampedBuildInfo() {
+ return stampedBuildInfo;
+ }
+
+ public ImmutableList<Artifact> getRedactedBuildInfo() {
+ return redactedBuildInfo;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/buildinfo/BuildInfoFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/buildinfo/BuildInfoFactory.java
new file mode 100644
index 0000000..c6ec4d7
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/buildinfo/BuildInfoFactory.java
@@ -0,0 +1,99 @@
+// 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.buildinfo;
+
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.io.Serializable;
+
+/**
+ * A factory for language-specific build-info files. Use this to translate the build-info into
+ * target-independent language-specific files. The generated actions are registered into the action
+ * graph on every build, but only executed if anything depends on them.
+ */
+public interface BuildInfoFactory extends Serializable {
+ /**
+ * Type of the build-data artifact.
+ */
+ public enum BuildInfoType {
+ /**
+ * Ignore changes to this file for the purposes of determining whether an action needs to be
+ * re-executed. I.e., the action is only re-executed if at least one other input has changed.
+ */
+ NO_REBUILD,
+
+ /**
+ * Changes to this file trigger re-execution of actions, similar to source file changes.
+ */
+ FORCE_REBUILD_IF_CHANGED;
+ }
+
+ /**
+ * Context for the creation of build-info artifacts.
+ */
+ public interface BuildInfoContext {
+ Artifact getBuildInfoArtifact(PathFragment rootRelativePath, Root root, BuildInfoType type);
+ Root getBuildDataDirectory();
+ }
+
+ /**
+ * Build-info key for lookup from the {@link
+ * com.google.devtools.build.lib.analysis.AnalysisEnvironment}.
+ */
+ public static final class BuildInfoKey implements Serializable {
+ private final String name;
+
+ public BuildInfoKey(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof BuildInfoKey)) {
+ return false;
+ }
+ return name.equals(((BuildInfoKey) o).name);
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+ }
+
+ /**
+ * Create actions and artifacts for language-specific build-info files.
+ */
+ BuildInfoCollection create(BuildInfoContext context, BuildConfiguration config,
+ Artifact buildInfo, Artifact buildChangelist);
+
+ /**
+ * Returns the key for the information created by this factory.
+ */
+ BuildInfoKey getKey();
+
+ /**
+ * Returns false if this build info factory is disabled based on the configuration (usually by
+ * checking if all required configuration fragments are present).
+ */
+ boolean isEnabled(BuildConfiguration config);
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BinTools.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BinTools.java
new file mode 100644
index 0000000..6d2477d
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BinTools.java
@@ -0,0 +1,191 @@
+// 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.config;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ArtifactFactory;
+import com.google.devtools.build.lib.actions.EnvironmentalExecException;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.vfs.Dirent;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.Symlinks;
+
+import java.io.IOException;
+
+/**
+ * Initializes the <execRoot>/_bin/ directory that contains auxiliary tools used during action
+ * execution (alarm, etc). The main purpose of this is to make sure that those tools are accessible
+ * using relative paths from the execution root.
+ */
+public final class BinTools {
+ private final BlazeDirectories directories;
+ private final Path binDir; // the working bin directory under execRoot
+ private final ImmutableList<String> embeddedTools;
+
+ private BinTools(BlazeDirectories directories, ImmutableList<String> tools) {
+ this.directories = directories;
+ this.binDir = directories.getExecRoot().getRelative("_bin");
+ this.embeddedTools = tools;
+ }
+
+ /**
+ * Creates an instance with the list of embedded tools obtained from scanning the directory
+ * into which said binaries were extracted by the launcher.
+ */
+ public static BinTools forProduction(BlazeDirectories directories) throws IOException {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ scanDirectoryRecursively(builder, directories.getEmbeddedBinariesRoot(), "");
+ return new BinTools(directories, builder.build());
+ }
+
+ /**
+ * Creates an empty instance for testing.
+ */
+ @VisibleForTesting
+ public static BinTools empty(BlazeDirectories directories) {
+ return new BinTools(directories, ImmutableList.<String>of());
+ }
+
+ /**
+ * Creates an instance for testing without actually symlinking the tools.
+ *
+ * <p>Used for tests that need a set of embedded tools to be present, but not the actual files.
+ */
+ @VisibleForTesting
+ public static BinTools forUnitTesting(BlazeDirectories directories, Iterable<String> tools) {
+ return new BinTools(directories, ImmutableList.copyOf(tools));
+ }
+
+ /**
+ * Populates the _bin directory by symlinking the necessary files from the given
+ * srcDir, and returns the corresponding BinTools.
+ */
+ @VisibleForTesting
+ public static BinTools forIntegrationTesting(
+ BlazeDirectories directories, String srcDir, Iterable<String> tools)
+ throws IOException {
+ Path srcPath = directories.getOutputBase().getFileSystem().getPath(srcDir);
+ for (String embedded : tools) {
+ Path runfilesPath = srcPath.getRelative(embedded);
+ if (!runfilesPath.isFile()) {
+ // The file isn't there - nothing to symlink!
+ //
+ // Note: This path is usually taken by the tests using the in-memory
+ // file system. They can't run the embedded scripts anyhow, so there isn't
+ // much point in creating a symlink to a non-existent binary here.
+ continue;
+ }
+ Path outputPath = directories.getExecRoot().getChild("_bin").getChild(embedded);
+ if (outputPath.exists()) {
+ outputPath.delete();
+ }
+ FileSystemUtils.createDirectoryAndParents(outputPath.getParentDirectory());
+ outputPath.createSymbolicLink(runfilesPath);
+ }
+
+ return new BinTools(directories, ImmutableList.copyOf(tools));
+ }
+
+ private static void scanDirectoryRecursively(
+ ImmutableList.Builder<String> result, Path root, String relative) throws IOException {
+ for (Dirent dirent : root.readdir(Symlinks.NOFOLLOW)) {
+ String childRelative = relative.isEmpty()
+ ? dirent.getName()
+ : relative + "/" + dirent.getName();
+ switch (dirent.getType()) {
+ case FILE:
+ result.add(childRelative);
+ break;
+
+ case DIRECTORY:
+ scanDirectoryRecursively(result, root.getChild(dirent.getName()), childRelative);
+ break;
+
+ default:
+ // Nothing to do here -- we ignore symlinks, since they should not be present in the
+ // embedded binaries tree.
+ break;
+ }
+ }
+ }
+
+ public PathFragment getExecPath(String embedPath) {
+ Preconditions.checkState(embeddedTools.contains(embedPath), "%s not in %s", embedPath,
+ embeddedTools);
+ return new PathFragment("_bin").getRelative(new PathFragment(embedPath).getBaseName());
+ }
+
+ public Artifact getEmbeddedArtifact(String embedPath, ArtifactFactory artifactFactory) {
+ return artifactFactory.getDerivedArtifact(getExecPath(embedPath));
+ }
+
+ public ImmutableList<Artifact> getAllEmbeddedArtifacts(ArtifactFactory artifactFactory) {
+ ImmutableList.Builder<Artifact> builder = ImmutableList.builder();
+ for (String embeddedTool : embeddedTools) {
+ builder.add(getEmbeddedArtifact(embeddedTool, artifactFactory));
+ }
+ return builder.build();
+ }
+
+ /**
+ * Initializes the build tools not available at absolute paths. Note that
+ * these must be constant across all configurations.
+ */
+ public void setupBuildTools() throws ExecException {
+ try {
+ FileSystemUtils.createDirectoryAndParents(binDir);
+ } catch (IOException e) {
+ throw new EnvironmentalExecException("could not create directory '" + binDir + "'", e);
+ }
+
+ for (String embeddedPath : embeddedTools) {
+ setupTool(embeddedPath);
+ }
+ }
+
+ private void setupTool(String embeddedPath) throws ExecException {
+ Path sourcePath = directories.getEmbeddedBinariesRoot().getRelative(embeddedPath);
+ Path linkPath = binDir.getRelative(new PathFragment(embeddedPath).getBaseName());
+ linkTool(sourcePath, linkPath);
+ }
+
+ private void linkTool(Path sourcePath, Path linkPath) throws ExecException {
+ if (linkPath.getFileSystem().supportsSymbolicLinks()) {
+ try {
+ if (!linkPath.isSymbolicLink()) {
+ // ensureSymbolicLink() does not handle the case where there is already
+ // a file with the same name, so we need to handle it here.
+ linkPath.delete();
+ }
+ FileSystemUtils.ensureSymbolicLink(linkPath, sourcePath);
+ } catch (IOException e) {
+ throw new EnvironmentalExecException("failed to link '" + sourcePath + "'", e);
+ }
+ } else {
+ // For file systems that do not support linking, copy.
+ try {
+ FileSystemUtils.copyTool(sourcePath, linkPath);
+ } catch (IOException e) {
+ throw new EnvironmentalExecException("failed to copy '" + sourcePath + "'" , e);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
new file mode 100644
index 0000000..8e80211
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
@@ -0,0 +1,1944 @@
+// 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.config;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ArrayListMultimap;
+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.ListMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.devtools.build.lib.actions.ArtifactFactory;
+import com.google.devtools.build.lib.actions.PackageRootResolver;
+import com.google.devtools.build.lib.actions.Root;
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.analysis.ViewCreationFailedException;
+import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection.Transitions;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.Attribute.Configurator;
+import com.google.devtools.build.lib.packages.Attribute.SplitTransition;
+import com.google.devtools.build.lib.packages.Attribute.Transition;
+import com.google.devtools.build.lib.packages.InputFile;
+import com.google.devtools.build.lib.packages.PackageGroup;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.rules.test.TestActionBuilder;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.Label.SyntaxException;
+import com.google.devtools.build.lib.syntax.SkylarkCallable;
+import com.google.devtools.build.lib.syntax.SkylarkModule;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.util.OS;
+import com.google.devtools.build.lib.util.RegexFilter;
+import com.google.devtools.build.lib.util.StringUtilities;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyFunction.Environment;
+import com.google.devtools.common.options.Converter;
+import com.google.devtools.common.options.Converters;
+import com.google.devtools.common.options.EnumConverter;
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionsBase;
+import com.google.devtools.common.options.OptionsParsingException;
+import com.google.devtools.common.options.TriState;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * Instances of BuildConfiguration represent a collection of context
+ * information which may affect a build (for example: the target platform for
+ * compilation, or whether or not debug tables are required). In fact, all
+ * "environmental" information (e.g. from the tool's command-line, as opposed
+ * to the BUILD file) that can affect the output of any build tool should be
+ * explicitly represented in the BuildConfiguration instance.
+ *
+ * <p>A single build may require building tools to run on a variety of
+ * platforms: when compiling a server application for production, we must build
+ * the build tools (like compilers) to run on the host platform, but cross-compile
+ * the application for the production environment.
+ *
+ * <p>There is always at least one BuildConfiguration instance in any build:
+ * the one representing the host platform. Additional instances may be created,
+ * in a cross-compilation build, for example.
+ *
+ * <p>Instances of BuildConfiguration are canonical:
+ * <pre>c1.equals(c2) <=> c1==c2.</pre>
+ */
+@SkylarkModule(name = "configuration",
+ doc = "Data required for the analysis of a target that comes from targets that "
+ + "depend on it and not targets that it depends on.")
+public final class BuildConfiguration implements Serializable {
+
+ /**
+ * An interface for language-specific configurations.
+ */
+ public abstract static class Fragment implements Serializable {
+ /**
+ * Returns a human-readable name of the configuration fragment.
+ */
+ public abstract String getName();
+
+ /**
+ * Validates the options for this Fragment. Issues warnings for the
+ * use of deprecated options, and warnings or errors for any option settings
+ * that conflict.
+ */
+ @SuppressWarnings("unused")
+ public void reportInvalidOptions(EventHandler reporter, BuildOptions buildOptions) {
+ }
+
+ /**
+ * Adds mapping of names to values of "Make" variables defined by this configuration.
+ */
+ @SuppressWarnings("unused")
+ public void addGlobalMakeVariables(ImmutableMap.Builder<String, String> globalMakeEnvBuilder) {
+ }
+
+ /**
+ * Collects all labels that should be implicitly loaded from labels that were specified as
+ * options, keyed by the name to be displayed to the user if something goes wrong.
+ * The resulting set only contains labels that were derived from command-line options; the
+ * intention is that it can be used to sanity-check that the command-line options actually
+ * contain these in their transitive closure.
+ */
+ @SuppressWarnings("unused")
+ public void addImplicitLabels(Multimap<String, Label> implicitLabels) {
+ }
+
+ /**
+ * Returns a string that identifies the configuration fragment.
+ */
+ public abstract String cacheKey();
+
+ /**
+ * The fragment may use this hook to perform I/O and read data into memory that is used during
+ * analysis. During the analysis phase disk I/O operations are disallowed.
+ *
+ * <p>This hook is only called for the top-level configuration after the loading phase is
+ * complete.
+ */
+ @SuppressWarnings("unused")
+ public void prepareHook(Path execPath, ArtifactFactory artifactFactory,
+ PathFragment genfilesPath, PackageRootResolver resolver)
+ throws ViewCreationFailedException {
+ }
+
+ /**
+ * Adds all the roots from this fragment.
+ */
+ @SuppressWarnings("unused")
+ public void addRoots(List<Root> roots) {
+ }
+
+ /**
+ * Returns a (key, value) mapping to insert into the subcommand environment for coverage.
+ */
+ public Map<String, String> getCoverageEnvironment() {
+ return ImmutableMap.<String, String>of();
+ }
+
+ /*
+ * Returns the command-line "Make" variable overrides.
+ */
+ public ImmutableMap<String, String> getCommandLineDefines() {
+ return ImmutableMap.of();
+ }
+
+ /**
+ * Returns all the coverage labels for the fragment.
+ */
+ public ImmutableList<Label> getCoverageLabels() {
+ return ImmutableList.of();
+ }
+
+ /**
+ * Returns the coverage report generator tool labels.
+ */
+ public ImmutableList<Label> getCoverageReportGeneratorLabels() {
+ return ImmutableList.of();
+ }
+
+ /**
+ * Returns a fragment of the output directory name for this configuration. The output
+ * directory for the whole configuration contains all the short names by all fragments.
+ */
+ @Nullable
+ public String getOutputDirectoryName() {
+ return null;
+ }
+
+ /**
+ * This will be added to the name of the configuration, but not to the output directory name.
+ */
+ @Nullable
+ public String getConfigurationNameSuffix() {
+ return null;
+ }
+
+ /**
+ * The platform name is a concatenation of fragment platform names.
+ */
+ public String getPlatformName() {
+ return "";
+ }
+
+ /**
+ * Return false if incremental build is not possible for some reason.
+ */
+ public boolean supportsIncrementalBuild() {
+ return true;
+ }
+
+ /**
+ * Return true if the fragment performs static linking. This information is needed for
+ * lincence checking.
+ */
+ public boolean performsStaticLink() {
+ return false;
+ }
+
+ /**
+ * Fragments should delete temporary directories they create for their inner mechanisms.
+ * This is only called for target configuration.
+ */
+ @SuppressWarnings("unused")
+ public void prepareForExecutionPhase() throws IOException {
+ }
+
+ /**
+ * Add items to the shell environment.
+ */
+ @SuppressWarnings("unused")
+ public void setupShellEnvironment(ImmutableMap.Builder<String, String> builder) {
+ }
+
+ /**
+ * Add mappings from generally available tool names (like "sh") to their paths
+ * that actions can access.
+ */
+ @SuppressWarnings("unused")
+ public void defineExecutables(ImmutableMap.Builder<String, PathFragment> builder) {
+ }
+
+ /**
+ * Returns { 'option name': 'alternative default' } entries for options where the
+ * "real default" should be something besides the default specified in the {@link Option}
+ * declaration.
+ */
+ public Map<String, Object> lateBoundOptionDefaults() {
+ return ImmutableMap.of();
+ }
+
+ /**
+ * Declares dependencies on any relevant Skyframe values (for example, relevant FileValues).
+ *
+ * @param env the skyframe environment
+ */
+ public void declareSkyframeDependencies(Environment env) {
+ }
+ }
+
+ /**
+ * A converter from strings to Labels.
+ */
+ public static class LabelConverter implements Converter<Label> {
+ @Override
+ public Label convert(String input) throws OptionsParsingException {
+ try {
+ // Check if the input starts with '/'. We don't check for "//" so that
+ // we get a better error message if the user accidentally tries to use
+ // an absolute path (starting with '/') for a label.
+ if (!input.startsWith("/")) {
+ input = "//" + input;
+ }
+ return Label.parseAbsolute(input);
+ } catch (SyntaxException e) {
+ throw new OptionsParsingException(e.getMessage());
+ }
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "a build target label";
+ }
+ }
+
+ public static class PluginOptionConverter implements Converter<Map.Entry<String, String>> {
+ @Override
+ public Map.Entry<String, String> convert(String input) throws OptionsParsingException {
+ int index = input.indexOf('=');
+ if (index == -1) {
+ throw new OptionsParsingException("Plugin option not in the plugin=option format");
+ }
+ String option = input.substring(0, index);
+ String value = input.substring(index + 1);
+ return Maps.immutableEntry(option, value);
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "An option for a plugin";
+ }
+ }
+
+ public static class RunsPerTestConverter extends PerLabelOptions.PerLabelOptionsConverter {
+ @Override
+ public PerLabelOptions convert(String input) throws OptionsParsingException {
+ try {
+ return parseAsInteger(input);
+ } catch (NumberFormatException ignored) {
+ return parseAsRegex(input);
+ }
+ }
+
+ private PerLabelOptions parseAsInteger(String input)
+ throws NumberFormatException, OptionsParsingException {
+ int numericValue = Integer.parseInt(input);
+ if (numericValue <= 0) {
+ throw new OptionsParsingException("'" + input + "' should be >= 1");
+ } else {
+ RegexFilter catchAll = new RegexFilter(Collections.singletonList(".*"),
+ Collections.<String>emptyList());
+ return new PerLabelOptions(catchAll, Collections.singletonList(input));
+ }
+ }
+
+ private PerLabelOptions parseAsRegex(String input) throws OptionsParsingException {
+ PerLabelOptions testRegexps = super.convert(input);
+ if (testRegexps.getOptions().size() != 1) {
+ throw new OptionsParsingException(
+ "'" + input + "' has multiple runs for a single pattern");
+ }
+ String runsPerTest = Iterables.getOnlyElement(testRegexps.getOptions());
+ try {
+ int numericRunsPerTest = Integer.parseInt(runsPerTest);
+ if (numericRunsPerTest <= 0) {
+ throw new OptionsParsingException("'" + input + "' has a value < 1");
+ }
+ } catch (NumberFormatException e) {
+ throw new OptionsParsingException("'" + input + "' has a non-numeric value", e);
+ }
+ return testRegexps;
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "a positive integer or test_regex@runs. This flag may be passed more than once";
+ }
+ }
+
+ /**
+ * Values for the --strict_*_deps option
+ */
+ public static enum StrictDepsMode {
+ /** Silently allow referencing transitive dependencies. */
+ OFF,
+ /** Warn about transitive dependencies being used directly. */
+ WARN,
+ /** Fail the build when transitive dependencies are used directly. */
+ ERROR,
+ /** Transition to strict by default. */
+ STRICT,
+ /** When no flag value is specified on the command line. */
+ DEFAULT
+ }
+
+ /**
+ * Converter for the --strict_*_deps option.
+ */
+ public static class StrictDepsConverter extends EnumConverter<StrictDepsMode> {
+ public StrictDepsConverter() {
+ super(StrictDepsMode.class, "strict dependency checking level");
+ }
+ }
+
+ /**
+ * Options that affect the value of a BuildConfiguration instance.
+ *
+ * <p>(Note: any client that creates a view will also need to declare
+ * BuildView.Options, which affect the <i>mechanism</i> of view construction,
+ * even if they don't affect the value of the BuildConfiguration instances.)
+ *
+ * <p>IMPORTANT: when adding new options, be sure to consider whether those
+ * values should be propagated to the host configuration or not (see
+ * {@link ConfigurationFactory#getConfiguration}.
+ *
+ * <p>ALSO IMPORTANT: all option types MUST define a toString method that
+ * gives identical results for semantically identical option values. The
+ * simplest way to ensure that is to return the input string.
+ */
+ public static class Options extends FragmentOptions implements Cloneable {
+ public String getCpu() {
+ return cpu;
+ }
+
+ @Option(name = "cpu",
+ defaultValue = "null",
+ category = "semantics",
+ help = "The target CPU.")
+ public String cpu;
+
+ @Option(name = "min_param_file_size",
+ defaultValue = "32768",
+ category = "undocumented",
+ help = "Minimum command line length before creating a parameter file.")
+ public int minParamFileSize;
+
+ @Option(name = "experimental_extended_sanity_checks",
+ defaultValue = "false",
+ category = "undocumented",
+ help = "Enables internal validation checks to make sure that configured target "
+ + "implementations only access things they should. Causes a performance hit.")
+ public boolean extendedSanityChecks;
+
+ @Option(name = "experimental_allow_runtime_deps_on_neverlink",
+ defaultValue = "true",
+ category = "undocumented",
+ help = "Flag to help transition from allowing to disallowing runtime_deps on neverlink"
+ + " Java archives. The depot needs to be cleaned up to roll this out by default.")
+ public boolean allowRuntimeDepsOnNeverLink;
+
+ @Option(name = "strict_filesets",
+ defaultValue = "false",
+ category = "semantics",
+ help = "If this option is enabled, filesets crossing package boundaries are reported "
+ + "as errors. It does not work when check_fileset_dependencies_recursively is "
+ + "disabled.")
+ public boolean strictFilesets;
+
+ // Plugins are build using the host config. To avoid cycles we just don't propagate
+ // this option to the host config. If one day we decide to use plugins when building
+ // host tools, we can improve this by (for example) creating a compiler configuration that is
+ // used only for building plugins.
+ @Option(name = "plugin",
+ converter = LabelConverter.class,
+ allowMultiple = true,
+ defaultValue = "",
+ category = "flags",
+ help = "Plugins to use in the build. Currently works with java_plugin.")
+ public List<Label> pluginList;
+
+ @Option(name = "plugin_copt",
+ converter = PluginOptionConverter.class,
+ allowMultiple = true,
+ category = "flags",
+ defaultValue = ":",
+ help = "Plugin options")
+ public List<Map.Entry<String, String>> pluginCoptList;
+
+ @Option(name = "stamp",
+ defaultValue = "true",
+ category = "semantics",
+ help = "Stamp binaries with the date, username, hostname, workspace information, etc.")
+ public boolean stampBinaries;
+
+ // TODO(bazel-team): delete from OSS tree
+ @Option(name = "instrumentation_filter",
+ converter = RegexFilter.RegexFilterConverter.class,
+ defaultValue = "-javatests,-_test$",
+ category = "semantics",
+ help = "When coverage is enabled, only rules with names included by the "
+ + "specified regex-based filter will be instrumented. Rules prefixed "
+ + "with '-' are excluded instead. By default, rules containing "
+ + "'javatests' or ending with '_test' will not be instrumented.")
+ public RegexFilter instrumentationFilter;
+
+ @Option(name = "show_cached_analysis_results",
+ defaultValue = "true",
+ category = "undocumented",
+ help = "Bazel reruns a static analysis only if it detects changes in the analysis "
+ + "or its dependencies. If this option is enabled, Bazel will show the analysis' "
+ + "results, even if it did not rerun the analysis. If this option is disabled, "
+ + "Bazel will show analysis results only if it reran the analysis.")
+ public boolean showCachedAnalysisResults;
+
+ @Option(name = "host_cpu",
+ defaultValue = "null",
+ category = "semantics",
+ help = "The host CPU.")
+ public String hostCpu;
+
+ @Option(name = "compilation_mode",
+ abbrev = 'c',
+ converter = CompilationMode.Converter.class,
+ defaultValue = "fastbuild",
+ category = "semantics", // Should this be "flags"?
+ help = "Specify the mode the binary will be built in. "
+ + "Values: 'fastbuild', 'dbg', 'opt'.")
+ public CompilationMode compilationMode;
+
+ /**
+ * This option is used internally to set the short name (see {@link
+ * #getShortName()}) of the <i>host</i> configuration to a constant, so
+ * that the output files for the host are completely independent of those
+ * for the target, no matter what options are in force (k8/piii, opt/dbg,
+ * etc).
+ */
+ @Option(name = "configuration short name", // (Spaces => can't be specified on command line.)
+ defaultValue = "null",
+ category = "undocumented")
+ public String shortName;
+
+ @Option(name = "platform_suffix",
+ defaultValue = "null",
+ category = "misc",
+ help = "Specifies a suffix to be added to the configuration directory.")
+ public String platformSuffix;
+
+ @Option(name = "test_env",
+ converter = Converters.OptionalAssignmentConverter.class,
+ allowMultiple = true,
+ defaultValue = "",
+ category = "testing",
+ help = "Specifies additional environment variables to be injected into the test runner "
+ + "environment. Variables can be either specified by name, in which case its value "
+ + "will be read from the Bazel client environment, or by the name=value pair. "
+ + "This option can be used multiple times to specify several variables. "
+ + "Used only by the 'bazel test' command."
+ )
+ public List<Map.Entry<String, String>> testEnvironment;
+
+ @Option(name = "collect_code_coverage",
+ defaultValue = "false",
+ category = "testing",
+ help = "If specified, Bazel will instrument code (using offline instrumentation where "
+ + "possible) and will collect coverage information during tests. Only targets that "
+ + " match --instrumentation_filter will be affected. Usually this option should "
+ + " not be specified directly - 'bazel coverage' command should be used instead."
+ )
+ public boolean collectCodeCoverage;
+
+ @Option(name = "microcoverage",
+ defaultValue = "false",
+ category = "testing",
+ help = "If specified with coverage, Blaze will collect microcoverage (per test method "
+ + "coverage) information during tests. Only targets that match "
+ + "--instrumentation_filter will be affected. Usually this option should not be "
+ + "specified directly - 'blaze coverage --microcoverage' command should be used "
+ + "instead."
+ )
+ public boolean collectMicroCoverage;
+
+ @Option(name = "cache_test_results",
+ defaultValue = "auto",
+ category = "testing",
+ abbrev = 't', // it's useful to toggle this on/off quickly
+ help = "If 'auto', Bazel will only rerun a test if any of the following conditions apply: "
+ + "(1) Bazel detects changes in the test or its dependencies "
+ + "(2) the test is marked as external "
+ + "(3) multiple test runs were requested with --runs_per_test"
+ + "(4) the test failed"
+ + "If 'yes', the caching behavior will be the same as 'auto' except that "
+ + "it may cache test failures and test runs with --runs_per_test."
+ + "If 'no', all tests will be always executed.")
+ public TriState cacheTestResults;
+
+ @Deprecated
+ @Option(name = "test_result_expiration",
+ defaultValue = "-1", // No expiration by defualt.
+ category = "testing",
+ help = "This option is deprecated and has no effect.")
+ public int testResultExpiration;
+
+ @Option(name = "test_sharding_strategy",
+ defaultValue = "explicit",
+ category = "testing",
+ converter = TestActionBuilder.ShardingStrategyConverter.class,
+ help = "Specify strategy for test sharding: "
+ + "'explicit' to only use sharding if the 'shard_count' BUILD attribute is present. "
+ + "'disabled' to never use test sharding. "
+ + "'experimental_heuristic' to enable sharding on remotely executed tests without an "
+ + "explicit 'shard_count' attribute which link in a supported framework. Considered "
+ + "experimental.")
+ public TestActionBuilder.TestShardingStrategy testShardingStrategy;
+
+ @Option(name = "runs_per_test",
+ allowMultiple = true,
+ defaultValue = "1",
+ category = "testing",
+ converter = RunsPerTestConverter.class,
+ help = "Specifies number of times to run each test. If any of those attempts "
+ + "fail for any reason, the whole test would be considered failed. "
+ + "Normally the value specified is just an integer. Example: --runs_per_test=3 "
+ + "will run all tests 3 times. "
+ + "Alternate syntax: regex_filter@runs_per_test. Where runs_per_test stands for "
+ + "an integer value and regex_filter stands "
+ + "for a list of include and exclude regular expression patterns (Also see "
+ + "--instrumentation_filter). Example: "
+ + "--runs_per_test=//foo/.*,-//foo/bar/.*@3 runs all tests in //foo/ "
+ + "except those under foo/bar three times. "
+ + "This option can be passed multiple times. ")
+ public List<PerLabelOptions> runsPerTest;
+
+ @Option(name = "build_runfile_links",
+ defaultValue = "true",
+ category = "strategy",
+ help = "If true, build runfiles symlink forests for all targets. "
+ + "If false, write only manifests when possible.")
+ public boolean buildRunfiles;
+
+ @Option(name = "test_arg",
+ allowMultiple = true,
+ defaultValue = "",
+ category = "testing",
+ help = "Specifies additional options and arguments that should be passed to the test "
+ + "executable. Can be used multiple times to specify several arguments. "
+ + "If multiple tests are executed, each of them will receive identical arguments. "
+ + "Used only by the 'bazel test' command."
+ )
+ public List<String> testArguments;
+
+ @Option(name = "test_filter",
+ allowMultiple = false,
+ defaultValue = "null",
+ category = "testing",
+ help = "Specifies a filter to forward to the test framework. Used to limit "
+ + "the tests run. Note that this does not affect which targets are built.")
+ public String testFilter;
+
+ @Option(name = "check_fileset_dependencies_recursively",
+ defaultValue = "true",
+ category = "semantics",
+ help = "If false, fileset targets will, whenever possible, create "
+ + "symlinks to directories instead of creating one symlink for each "
+ + "file inside the directory. Disabling this will significantly "
+ + "speed up fileset builds, but targets that depend on filesets will "
+ + "not be rebuilt if files are added, removed or modified in a "
+ + "subdirectory which has not been traversed.")
+ public boolean checkFilesetDependenciesRecursively;
+
+ @Option(name = "run_under",
+ category = "run",
+ defaultValue = "null",
+ converter = RunUnderConverter.class,
+ help = "Prefix to insert in front of command before running. "
+ + "Examples:\n"
+ + "\t--run_under=valgrind\n"
+ + "\t--run_under=strace\n"
+ + "\t--run_under='strace -c'\n"
+ + "\t--run_under='valgrind --quiet --num-callers=20'\n"
+ + "\t--run_under=//package:target\n"
+ + "\t--run_under='//package:target --options'\n")
+ public RunUnder runUnder;
+
+ @Option(name = "distinct_host_configuration",
+ defaultValue = "true",
+ category = "strategy",
+ help = "Build all the tools used during the build for a distinct configuration from "
+ + "that used for the target program. By default, the same configuration is used "
+ + "for host and target programs, but this may cause undesirable rebuilds of tool "
+ + "such as the protocol compiler (and then everything downstream) whenever a minor "
+ + "change is made to the target configuration, such as setting the linker options. "
+ + "When this flag is specified, a distinct configuration will be used to build the "
+ + "tools, preventing undesired rebuilds. However, certain libraries will then "
+ + "need to be compiled twice, once for each configuration, which may cause some "
+ + "builds to be slower. As a rule of thumb, this option is likely to benefit "
+ + "users that make frequent changes in configuration (e.g. opt/dbg). "
+ + "Please read the user manual for the full explanation.")
+ public boolean useDistinctHostConfiguration;
+
+ @Option(name = "check_visibility",
+ defaultValue = "true",
+ category = "checking",
+ help = "If disabled, visibility errors are demoted to warnings.")
+ public boolean checkVisibility;
+
+ // Moved from viewOptions to here because license information is very expensive to serialize.
+ // Having it here allows us to skip computation of transitive license information completely
+ // when the setting is disabled.
+ @Option(name = "check_licenses",
+ defaultValue = "false",
+ category = "checking",
+ help = "Check that licensing constraints imposed by dependent packages "
+ + "do not conflict with distribution modes of the targets being built. "
+ + "By default, licenses are not checked.")
+ public boolean checkLicenses;
+
+ @Option(name = "experimental_enforce_constraints",
+ defaultValue = "true",
+ category = "undocumented",
+ help = "Checks the environments each target is compatible with and reports errors if any "
+ + "target has dependencies that don't support the same environments")
+ public boolean enforceConstraints;
+
+ @Option(name = "experimental_action_listener",
+ allowMultiple = true,
+ defaultValue = "",
+ category = "experimental",
+ converter = LabelConverter.class,
+ help = "Use action_listener to attach an extra_action to existing build actions.")
+ public List<Label> actionListeners;
+
+ @Option(name = "is host configuration",
+ defaultValue = "false",
+ category = "undocumented",
+ help = "Shows whether these options are set for host configuration.")
+ public boolean isHost;
+
+ @Option(name = "experimental_proto_header_modules",
+ defaultValue = "false",
+ category = "undocumented",
+ help = "Enables compilation of C++ header modules for proto libraries.")
+ public boolean protoHeaderModules;
+
+ @Option(name = "features",
+ allowMultiple = true,
+ defaultValue = "",
+ category = "flags",
+ help = "The given features will be enabled or disabled by default for all packages. "
+ + "Specifying -<feature> will disable the feature globally. "
+ + "Negative features always override positive ones. "
+ + "This flag is used to enable rolling out default feature changes without a "
+ + "Blaze release.")
+ public List<String> defaultFeatures;
+
+ @Override
+ public FragmentOptions getHost(boolean fallback) {
+ Options host = (Options) getDefault();
+
+ host.shortName = "host";
+ host.compilationMode = CompilationMode.OPT;
+ host.isHost = true;
+
+ if (fallback) {
+ // In the fallback case, we have already tried the target options and they didn't work, so
+ // now we try the default options; the hostCpu field has the default value, because we use
+ // getDefault() above.
+ host.cpu = computeHostCpu(host.hostCpu);
+ } else {
+ host.cpu = computeHostCpu(hostCpu);
+ }
+
+ // === Runfiles ===
+ // Ideally we could force this the other way, and skip runfiles construction
+ // for host tools which are never run locally, but that's probably a very
+ // small optimization.
+ host.buildRunfiles = true;
+
+ // === Linkstamping ===
+ // Disable all link stamping for the host configuration, to improve action
+ // cache hit rates for tools.
+ host.stampBinaries = false;
+
+ // === Visibility ===
+ host.checkVisibility = checkVisibility;
+
+ // === Licenses ===
+ host.checkLicenses = checkLicenses;
+
+ // === Allow runtime_deps to depend on neverlink Java libraries.
+ host.allowRuntimeDepsOnNeverLink = allowRuntimeDepsOnNeverLink;
+
+ // === Pass on C++ compiler features.
+ host.defaultFeatures = ImmutableList.copyOf(defaultFeatures);
+
+ return host;
+ }
+
+ private static String computeHostCpu(String explicitHostCpu) {
+ if (explicitHostCpu != null) {
+ return explicitHostCpu;
+ }
+ switch (OS.getCurrent()) {
+ case DARWIN:
+ return "darwin";
+ default:
+ return "k8";
+ }
+ }
+
+ @Override
+ public void addAllLabels(Multimap<String, Label> labelMap) {
+ labelMap.putAll("action_listener", actionListeners);
+ labelMap.putAll("plugins", pluginList);
+ if ((runUnder != null) && (runUnder.getLabel() != null)) {
+ labelMap.put("RunUnder", runUnder.getLabel());
+ }
+ }
+ }
+
+ /**
+ * A list of build configurations that only contains the null element.
+ */
+ private static final List<BuildConfiguration> NULL_LIST =
+ Collections.unmodifiableList(Arrays.asList(new BuildConfiguration[] { null }));
+
+ private final String cacheKey;
+ private final String shortCacheKey;
+
+ private Transitions transitions;
+ private Set<BuildConfiguration> allReachableConfigurations;
+
+ private final ImmutableMap<Class<? extends Fragment>, Fragment> fragments;
+
+ // Directories in the output tree
+ private final Root outputDirectory; // the configuration-specific output directory.
+ private final Root binDirectory;
+ private final Root genfilesDirectory;
+ private final Root coverageMetadataDirectory; // for coverage-related metadata, artifacts, etc.
+ private final Root testLogsDirectory;
+ private final Root includeDirectory;
+ private final Root middlemanDirectory;
+
+ private final PathFragment binFragment;
+ private final PathFragment genfilesFragment;
+
+ // If false, AnalysisEnviroment doesn't register any actions created by the ConfiguredTarget.
+ private final boolean actionsEnabled;
+
+ private final ImmutableSet<Label> coverageLabels;
+ private final ImmutableSet<Label> coverageReportGeneratorLabels;
+
+ // Executables like "perl" or "sh"
+ private final ImmutableMap<String, PathFragment> executables;
+
+ // All the "defglobals" in //tools:GLOBALS for this platform/configuration:
+ private final ImmutableMap<String, String> globalMakeEnv;
+
+ private final ImmutableMap<String, String> defaultShellEnvironment;
+ private final BuildOptions buildOptions;
+ private final Options options;
+
+ private final String shortName;
+ private final String mnemonic;
+ private final String platformName;
+
+ /**
+ * It is not fingerprinted because it should only be used to access
+ * variables that do not break the hermetism of build rules.
+ */
+ private final ImmutableMap<String, String> clientEnvironment;
+
+ /**
+ * Helper container for {@link #transitiveOptionsMap} below.
+ */
+ private static class OptionDetails implements Serializable {
+ private OptionDetails(Class<? extends OptionsBase> optionsClass, Object value,
+ boolean allowsMultiple) {
+ this.optionsClass = optionsClass;
+ this.value = value;
+ this.allowsMultiple = allowsMultiple;
+ }
+
+ /** The {@link FragmentOptions} class that defines this option. */
+ private final Class<? extends OptionsBase> optionsClass;
+
+ /**
+ * The value of the given option (either explicitly defined or default). May be null.
+ */
+ private final Object value;
+
+ /** Whether or not this option supports multiple values. */
+ private final boolean allowsMultiple;
+ }
+
+ /**
+ * Maps option names to the {@link OptionDetails} the option takes for this configuration.
+ *
+ * <p>This can be used to:
+ * <ol>
+ * <li>Find an option's (parsed) value given its command-line name</li>
+ * <li>Parse alternative values for the option.</li>
+ * </ol>
+ *
+ * <p>This map is "transitive" in that it includes *all* options recognizable by this
+ * configuration, including those defined in child fragments.
+ */
+ private final Map<String, OptionDetails> transitiveOptionsMap;
+
+
+ /**
+ * Validates the options for this BuildConfiguration. Issues warnings for the
+ * use of deprecated options, and warnings or errors for any option settings
+ * that conflict.
+ */
+ public void reportInvalidOptions(EventHandler reporter) {
+ for (Fragment fragment : fragments.values()) {
+ fragment.reportInvalidOptions(reporter, this.buildOptions);
+ }
+
+ Set<String> plugins = new HashSet<>();
+ for (Label plugin : options.pluginList) {
+ String name = plugin.getName();
+ if (plugins.contains(name)) {
+ reporter.handle(Event.error("A build cannot have two plugins with the same name"));
+ }
+ plugins.add(name);
+ }
+ for (Map.Entry<String, String> opt : options.pluginCoptList) {
+ if (!plugins.contains(opt.getKey())) {
+ reporter.handle(Event.error("A plugin_copt must refer to an existing plugin"));
+ }
+ }
+
+ if (options.shortName != null) {
+ reporter.handle(Event.error(
+ "The internal '--configuration short name' option cannot be used on the command line"));
+ }
+
+ if (options.testShardingStrategy
+ == TestActionBuilder.TestShardingStrategy.EXPERIMENTAL_HEURISTIC) {
+ reporter.handle(Event.warn(
+ "Heuristic sharding is intended as a one-off experimentation tool for determing the "
+ + "benefit from sharding certain tests. Please don't keep this option in your "
+ + ".blazerc or continuous build"));
+ }
+ }
+
+ private ImmutableMap<String, String> setupShellEnvironment() {
+ ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
+ for (Fragment fragment : fragments.values()) {
+ fragment.setupShellEnvironment(builder);
+ }
+ return builder.build();
+ }
+
+ BuildConfiguration(BlazeDirectories directories,
+ Map<Class<? extends Fragment>, Fragment> fragmentsMap,
+ BuildOptions buildOptions,
+ Map<String, String> clientEnv,
+ boolean actionsDisabled) {
+ this.actionsEnabled = !actionsDisabled;
+ fragments = ImmutableMap.copyOf(fragmentsMap);
+
+ // This is a view that will be updated upon each client command.
+ this.clientEnvironment = ImmutableMap.copyOf(clientEnv);
+
+ this.buildOptions = buildOptions;
+ this.options = buildOptions.get(Options.class);
+
+ this.mnemonic = buildMnemonic();
+ String outputDirName = (options.shortName != null) ? options.shortName : mnemonic;
+ this.shortName = buildShortName(outputDirName);
+
+ this.executables = collectExecutables();
+
+ Path execRoot = directories.getExecRoot();
+ // configuration-specific output tree
+ Path outputDir = directories.getOutputPath().getRelative(outputDirName);
+ this.outputDirectory = Root.asDerivedRoot(execRoot, outputDir);
+
+ // specific subdirs under outputDirectory
+ this.binDirectory = Root.asDerivedRoot(execRoot, outputDir.getRelative("bin"));
+ this.genfilesDirectory = Root.asDerivedRoot(execRoot, outputDir.getRelative("genfiles"));
+ this.coverageMetadataDirectory = Root.asDerivedRoot(execRoot,
+ outputDir.getRelative("coverage-metadata"));
+ this.testLogsDirectory = Root.asDerivedRoot(execRoot, outputDir.getRelative("testlogs"));
+ this.includeDirectory = Root.asDerivedRoot(execRoot,
+ outputDir.getRelative(BlazeDirectories.RELATIVE_INCLUDE_DIR));
+ this.middlemanDirectory = Root.middlemanRoot(execRoot, outputDir);
+
+ // precompute some frequently-used relative paths
+ this.binFragment = getBinDirectory().getExecPath();
+ this.genfilesFragment = getGenfilesDirectory().getExecPath();
+
+ ImmutableSet.Builder<Label> coverageLabelsBuilder = ImmutableSet.builder();
+ ImmutableSet.Builder<Label> coverageReportGeneratorLabelsBuilder = ImmutableSet.builder();
+ for (Fragment fragment : fragments.values()) {
+ coverageLabelsBuilder.addAll(fragment.getCoverageLabels());
+ coverageReportGeneratorLabelsBuilder.addAll(fragment.getCoverageReportGeneratorLabels());
+ }
+ this.coverageLabels = coverageLabelsBuilder.build();
+ this.coverageReportGeneratorLabels = coverageReportGeneratorLabelsBuilder.build();
+
+ // Platform name
+ StringBuilder platformNameBuilder = new StringBuilder();
+ for (Fragment fragment : fragments.values()) {
+ platformNameBuilder.append(fragment.getPlatformName());
+ }
+ this.platformName = platformNameBuilder.toString();
+
+ this.defaultShellEnvironment = setupShellEnvironment();
+
+ this.transitiveOptionsMap = computeOptionsMap(buildOptions, fragments.values());
+
+ ImmutableMap.Builder<String, String> globalMakeEnvBuilder = ImmutableMap.builder();
+ for (Fragment fragment : fragments.values()) {
+ fragment.addGlobalMakeVariables(globalMakeEnvBuilder);
+ }
+
+ // Lots of packages in third_party assume that BINMODE expands to either "-dbg", or "-opt". So
+ // for backwards compatibility we preserve that invariant, setting BINMODE to "-dbg" rather than
+ // "-fastbuild" if the compilation mode is "fastbuild".
+ // We put the real compilation mode in a new variable COMPILATION_MODE.
+ globalMakeEnvBuilder.put("COMPILATION_MODE", options.compilationMode.toString());
+ globalMakeEnvBuilder.put("BINMODE", "-"
+ + ((options.compilationMode == CompilationMode.FASTBUILD)
+ ? "dbg"
+ : options.compilationMode.toString()));
+ /*
+ * Attention! Document these in the build-encyclopedia
+ */
+ // the bin directory and the genfiles directory
+ // These variables will be used on Windows as well, so we need to make sure
+ // that paths use the correct system file-separator.
+ globalMakeEnvBuilder.put("BINDIR", binFragment.getPathString());
+ globalMakeEnvBuilder.put("INCDIR",
+ getIncludeDirectory().getExecPath().getPathString());
+ globalMakeEnvBuilder.put("GENDIR", genfilesFragment.getPathString());
+ globalMakeEnv = globalMakeEnvBuilder.build();
+
+ cacheKey = computeCacheKey(
+ directories, fragmentsMap, this.buildOptions, this.clientEnvironment);
+ shortCacheKey = shortName + "-" + Fingerprint.md5Digest(cacheKey);
+ }
+
+
+ /**
+ * Computes and returns the transitive optionName -> "option info" map for
+ * this configuration.
+ */
+ private static Map<String, OptionDetails> computeOptionsMap(BuildOptions buildOptions,
+ Iterable<Fragment> fragments) {
+ // Collect from our fragments "alternative defaults" for options where the default
+ // should be something other than what's specified in Option.defaultValue.
+ Map<String, Object> lateBoundDefaults = Maps.newHashMap();
+ for (Fragment fragment : fragments) {
+ lateBoundDefaults.putAll(fragment.lateBoundOptionDefaults());
+ }
+
+ ImmutableMap.Builder<String, OptionDetails> map = ImmutableMap.builder();
+ try {
+ for (FragmentOptions options : buildOptions.getOptions()) {
+ for (Field field : options.getClass().getFields()) {
+ if (field.isAnnotationPresent(Option.class)) {
+ Option option = field.getAnnotation(Option.class);
+ Object value = field.get(options);
+ if (value == null) {
+ if (lateBoundDefaults.containsKey(option.name())) {
+ value = lateBoundDefaults.get(option.name());
+ } else if (!option.defaultValue().equals("null")) {
+ // See {@link Option#defaultValue} for an explanation of default "null" strings.
+ value = option.defaultValue();
+ }
+ }
+ map.put(option.name(),
+ new OptionDetails(options.getClass(), value, option.allowMultiple()));
+ }
+ }
+ }
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException(
+ "Unexpected illegal access trying to create this configuration's options map: ", e);
+ }
+ return map.build();
+ }
+
+ private String buildShortName(String outputDirName) {
+ ArrayList<String> nameParts = new ArrayList<>(ImmutableList.of(outputDirName));
+ for (Fragment fragment : fragments.values()) {
+ nameParts.add(fragment.getConfigurationNameSuffix());
+ }
+ return Joiner.on('-').skipNulls().join(nameParts);
+ }
+
+ private String buildMnemonic() {
+ // See explanation at getShortName().
+ String platformSuffix = (options.platformSuffix != null) ? options.platformSuffix : "";
+ ArrayList<String> nameParts = new ArrayList<String>();
+ for (Fragment fragment : fragments.values()) {
+ nameParts.add(fragment.getOutputDirectoryName());
+ }
+ nameParts.add(getCompilationMode() + platformSuffix);
+ return Joiner.on('-').join(Iterables.filter(nameParts, Predicates.notNull()));
+ }
+
+ /**
+ * Set the outgoing configuration transitions. During the lifetime of a given build configuration,
+ * this must happen exactly once, shortly after the configuration is created.
+ * TODO(bazel-team): this makes the object mutable, get rid of it.
+ */
+ public void setConfigurationTransitions(Transitions transitions) {
+ Preconditions.checkNotNull(transitions);
+ Preconditions.checkState(this.transitions == null);
+ this.transitions = transitions;
+ }
+
+ public Transitions getTransitions() {
+ Preconditions.checkState(this.transitions != null || isHostConfiguration());
+ return transitions;
+ }
+
+ /**
+ * Returns all configurations that can be reached from this configuration through any kind of
+ * configuration transition.
+ */
+ public synchronized Collection<BuildConfiguration> getAllReachableConfigurations() {
+ if (allReachableConfigurations == null) {
+ // This is needed for every configured target in skyframe m2, so we cache it.
+ // We could alternatively make the corresponding dependencies into a skyframe node.
+ this.allReachableConfigurations = computeAllReachableConfigurations();
+ }
+ return allReachableConfigurations;
+ }
+
+ /**
+ * Returns all configurations that can be reached from this configuration through any kind of
+ * configuration transition.
+ */
+ private Set<BuildConfiguration> computeAllReachableConfigurations() {
+ Set<BuildConfiguration> result = new LinkedHashSet<>();
+ Queue<BuildConfiguration> queue = new LinkedList<>();
+ queue.add(this);
+ while (!queue.isEmpty()) {
+ BuildConfiguration config = queue.remove();
+ if (!result.add(config)) {
+ continue;
+ }
+ config.getTransitions().addDirectlyReachableConfigurations(queue);
+ }
+ return result;
+ }
+
+ /**
+ * Returns the new configuration after traversing a dependency edge with a given configuration
+ * transition.
+ *
+ * @param transition the configuration transition
+ * @return the new configuration
+ * @throws IllegalArgumentException if the transition is a {@link SplitTransition}
+ */
+ public BuildConfiguration getConfiguration(Transition transition) {
+ Preconditions.checkArgument(!(transition instanceof SplitTransition));
+ return transitions.getConfiguration(transition);
+ }
+
+ /**
+ * Returns the new configurations after traversing a dependency edge with a given split
+ * transition.
+ *
+ * @param transition the split configuration transition
+ * @return the new configurations
+ */
+ public List<BuildConfiguration> getSplitConfigurations(SplitTransition<?> transition) {
+ return transitions.getSplitConfigurations(transition);
+ }
+
+ /**
+ * Calculates the configurations of a direct dependency. If a rule in some BUILD file refers
+ * to a target (like another rule or a source file) using a label attribute, that target needs
+ * to have a configuration, too. This method figures out the proper configuration for the
+ * dependency.
+ *
+ * @param fromRule the rule that's depending on some target
+ * @param attribute the attribute using which the rule depends on that target (eg. "srcs")
+ * @param toTarget the target that's dependeded on
+ * @return the configuration that should be associated to {@code toTarget}
+ */
+ public Iterable<BuildConfiguration> evaluateTransition(final Rule fromRule,
+ final Attribute attribute, final Target toTarget) {
+ // Fantastic configurations and where to find them:
+
+ // I. Input files and package groups have no configurations. We don't want to duplicate them.
+ if (toTarget instanceof InputFile || toTarget instanceof PackageGroup) {
+ return NULL_LIST;
+ }
+
+ // II. Host configurations never switch to another. All prerequisites of host targets have the
+ // same host configuration.
+ if (isHostConfiguration()) {
+ return ImmutableList.of(this);
+ }
+
+ // Make sure config_setting dependencies are resolved in the referencing rule's configuration,
+ // unconditionally. For example, given:
+ //
+ // genrule(
+ // name = 'myrule',
+ // tools = select({ '//a:condition': [':sometool'] })
+ //
+ // all labels in "tools" get resolved in the host configuration (since the "tools" attribute
+ // declares a host configuration transition). We want to explicitly exclude configuration labels
+ // from these transitions, since their *purpose* is to do computation on the owning
+ // rule's configuration.
+ // TODO(bazel-team): implement this more elegantly. This is far too hackish. Specifically:
+ // don't reference the rule name explicitly and don't require special-casing here.
+ if (toTarget instanceof Rule && ((Rule) toTarget).getRuleClass().equals("config_setting")) {
+ return ImmutableList.of(this);
+ }
+
+ List<BuildConfiguration> toConfigurations;
+ if (attribute.getConfigurationTransition() instanceof SplitTransition) {
+ Preconditions.checkState(attribute.getConfigurator() == null);
+ toConfigurations = getSplitConfigurations(
+ (SplitTransition<?>) attribute.getConfigurationTransition());
+ } else {
+ // III. Attributes determine configurations. The configuration of a prerequisite is determined
+ // by the attribute.
+ @SuppressWarnings("unchecked")
+ Configurator<BuildConfiguration, Rule> configurator =
+ (Configurator<BuildConfiguration, Rule>) attribute.getConfigurator();
+ toConfigurations = ImmutableList.of((configurator != null)
+ ? configurator.apply(fromRule, this, attribute, toTarget)
+ : getConfiguration(attribute.getConfigurationTransition()));
+ }
+
+ return Iterables.transform(toConfigurations,
+ new Function<BuildConfiguration, BuildConfiguration>() {
+ @Override
+ public BuildConfiguration apply(BuildConfiguration input) {
+ // IV. Allow the transition object to perform an arbitrary switch. Blaze modules can inject
+ // configuration transition logic by extending the Transitions class.
+ BuildConfiguration actual = getTransitions().configurationHook(
+ fromRule, attribute, toTarget, input);
+
+ // V. Allow rule classes to override their own configurations.
+ Rule associatedRule = toTarget.getAssociatedRule();
+ if (associatedRule != null) {
+ @SuppressWarnings("unchecked")
+ RuleClass.Configurator<BuildConfiguration, Rule> func =
+ associatedRule.getRuleClassObject().<BuildConfiguration, Rule>getConfigurator();
+ actual = func.apply(associatedRule, actual);
+ }
+
+ return actual;
+ }
+ });
+ }
+
+ /**
+ * Returns a multimap of all labels that should be implicitly loaded from labels that were
+ * specified as options, keyed by the name to be displayed to the user if something goes wrong.
+ * The returned set only contains labels that were derived from command-line options; the
+ * intention is that it can be used to sanity-check that the command-line options actually contain
+ * these in their transitive closure.
+ */
+ public ListMultimap<String, Label> getImplicitLabels() {
+ ListMultimap<String, Label> implicitLabels = ArrayListMultimap.create();
+ for (Fragment fragment : fragments.values()) {
+ fragment.addImplicitLabels(implicitLabels);
+ }
+ return implicitLabels;
+ }
+
+ /**
+ * For an given environment, returns a subset containing all
+ * variables in the given list if they are defined in the given
+ * environment.
+ */
+ @VisibleForTesting
+ static Map<String, String> getMapping(List<String> variables,
+ Map<String, String> environment) {
+ Map<String, String> result = new HashMap<>();
+ for (String var : variables) {
+ if (environment.containsKey(var)) {
+ result.put(var, environment.get(var));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Avoid this method. The client environment is not part of the configuration's signature, so
+ * calls to this method introduce a non-hermetic access to data that is not visible to Skyframe.
+ *
+ * @return an unmodifiable view of the bazel client's environment
+ * upon its most recent request.
+ */
+ // TODO(bazel-team): Remove this.
+ public Map<String, String> getClientEnv() {
+ return clientEnvironment;
+ }
+
+ /**
+ * Returns the {@link Option} class the defines the given option, null if the
+ * option isn't recognized.
+ *
+ * <p>optionName is the name of the option as it appears on the command line
+ * e.g. {@link Option#name}).
+ */
+ Class<? extends OptionsBase> getOptionClass(String optionName) {
+ OptionDetails optionData = transitiveOptionsMap.get(optionName);
+ return optionData == null ? null : optionData.optionsClass;
+ }
+
+ /**
+ * Returns the value of the specified option for this configuration or null if the
+ * option isn't recognized. Since an option's legitimate value could be null, use
+ * {@link #getOptionClass} to distinguish between that and an unknown option.
+ *
+ * <p>optionName is the name of the option as it appears on the command line
+ * e.g. {@link Option#name}).
+ */
+ Object getOptionValue(String optionName) {
+ OptionDetails optionData = transitiveOptionsMap.get(optionName);
+ return (optionData == null) ? null : optionData.value;
+ }
+
+ /**
+ * Returns whether or not the given option supports multiple values at the command line (e.g.
+ * "--myoption value1 --myOption value2 ..."). Returns false for unrecognized options. Use
+ * {@link #getOptionClass} to distinguish between those and legitimate single-value options.
+ *
+ * <p>As declared in {@link Option#allowMultiple}, multi-value options are expected to be
+ * of type {@code List<T>}.
+ */
+ boolean allowsMultipleValues(String optionName) {
+ OptionDetails optionData = transitiveOptionsMap.get(optionName);
+ return (optionData == null) ? false : optionData.allowsMultiple;
+ }
+
+ /**
+ * The platform string, suitable for use as a key into a MakeEnvironment.
+ */
+ public String getPlatformName() {
+ return platformName;
+ }
+
+ /**
+ * Returns the output directory for this build configuration.
+ */
+ public Root getOutputDirectory() {
+ return outputDirectory;
+ }
+
+ /**
+ * Returns the bin directory for this build configuration.
+ */
+ @SkylarkCallable(name = "bin_dir", structField = true,
+ doc = "The root corresponding to bin directory.")
+ public Root getBinDirectory() {
+ return binDirectory;
+ }
+
+ /**
+ * Returns a relative path to the bin directory at execution time.
+ */
+ public PathFragment getBinFragment() {
+ return binFragment;
+ }
+
+ /**
+ * Returns the include directory for this build configuration.
+ */
+ public Root getIncludeDirectory() {
+ return includeDirectory;
+ }
+
+ /**
+ * Returns the genfiles directory for this build configuration.
+ */
+ @SkylarkCallable(name = "genfiles_dir", structField = true,
+ doc = "The root corresponding to genfiles directory.")
+ public Root getGenfilesDirectory() {
+ return genfilesDirectory;
+ }
+
+ /**
+ * Returns the directory where coverage-related artifacts and metadata files
+ * should be stored. This includes for example uninstrumented class files
+ * needed for Jacoco's coverage reporting tools.
+ */
+ public Root getCoverageMetadataDirectory() {
+ return coverageMetadataDirectory;
+ }
+
+ /**
+ * Returns the testlogs directory for this build configuration.
+ */
+ public Root getTestLogsDirectory() {
+ return testLogsDirectory;
+ }
+
+ /**
+ * Returns a relative path to the genfiles directory at execution time.
+ */
+ public PathFragment getGenfilesFragment() {
+ return genfilesFragment;
+ }
+
+ /**
+ * Returns the path separator for the host platform. This is basically the same as {@link
+ * java.io.File#pathSeparator}, except that that returns the value for this JVM, which may or may
+ * not match the host platform. You should only use this when invoking tools that are known to use
+ * the native path separator, i.e., the path separator for the machine that they run on.
+ */
+ @SkylarkCallable(name = "host_path_separator", structField = true,
+ doc = "Returns the separator for PATH variable, which is ':' on Unix.")
+ public String getHostPathSeparator() {
+ // TODO(bazel-team): This needs to change when we support Windows.
+ return ":";
+ }
+
+ /**
+ * Returns the internal directory (used for middlemen) for this build configuration.
+ */
+ public Root getMiddlemanDirectory() {
+ return middlemanDirectory;
+ }
+
+ public boolean getAllowRuntimeDepsOnNeverLink() {
+ return options.allowRuntimeDepsOnNeverLink;
+ }
+
+ public boolean isStrictFilesets() {
+ return options.strictFilesets;
+ }
+
+ public List<Label> getPlugins() {
+ return options.pluginList;
+ }
+
+ public List<Map.Entry<String, String>> getPluginCopts() {
+ return options.pluginCoptList;
+ }
+
+ /**
+ * Implements a non-injective mapping from BuildConfiguration instances to
+ * strings. The result should identify the aspects of the configuration
+ * that should be reflected in the output file names. Furthermore the
+ * returned string must not contain shell metacharacters.
+ *
+ * <p>The intention here is that we use this string as the directory name
+ * for artifacts of this build.
+ *
+ * <p>For configuration settings which are NOT part of the short name,
+ * rebuilding with a different value of such a setting will build in
+ * the same output directory. This means that any actions whose
+ * keys (see Action.getKey()) have changed will be rerun. That
+ * may result in a lot of recompilation.
+ *
+ * <p>For configuration settings which ARE part of the short name,
+ * rebuilding with a different value of such a setting will rebuild
+ * in a different output directory; this will result in higher disk
+ * usage and more work the _first_ time you rebuild with a different
+ * setting, but will result in less work if you regularly switch
+ * back and forth between different settings.
+ *
+ * <p>With one important exception, it's sound to choose any subset of the
+ * config's components for this string, it just alters the dimensionality
+ * of the cache. In other words, it's a trade-off on the "injectiveness"
+ * scale: at one extreme (shortName is in fact a complete fingerprint, and
+ * thus injective) you get extremely precise caching (no competition for the
+ * same output-file locations) but you have to rebuild for even the
+ * slightest change in configuration. At the other extreme
+ * (PartialFingerprint is a constant) you have very high competition for
+ * output-file locations, but if a slight change in configuration doesn't
+ * affect a particular build step, you're guaranteed not to have to
+ * rebuild it. The important exception has to do with cross-compilation:
+ * the host and target configurations must not map to the same output
+ * directory, because then files would need to get built for the host
+ * and then rebuilt for the target even within a single build, and that
+ * wouldn't work.
+ *
+ * <p>Just to re-iterate: cross-compilation builds (i.e. hostConfig !=
+ * targetConfig) will not work if the two configurations' short names are
+ * equal. This is an important practical case: the mere addition of
+ * a compile flag to the target configuration would cause the build to
+ * fail. In other words, it would break if the host and target
+ * configurations are not identical but are "too close". The current
+ * solution is to set the host configuration equal to the target
+ * configuration if they are "too close"; this may cause the tools to get
+ * rebuild for the new host configuration though.
+ */
+ public String getShortName() {
+ return shortName;
+ }
+
+ /**
+ * Like getShortName(), but always returns a configuration-dependent string even for
+ * the host configuration.
+ */
+ public String getMnemonic() {
+ return mnemonic;
+ }
+
+ @Override
+ public String toString() {
+ return getShortName();
+ }
+
+ /**
+ * Returns the default shell environment
+ */
+ @SkylarkCallable(name = "default_shell_env", structField = true,
+ doc = "A dictionary representing the default environment. It maps variables "
+ + "to their values (strings).")
+ public ImmutableMap<String, String> getDefaultShellEnvironment() {
+ return defaultShellEnvironment;
+ }
+
+ /**
+ * Returns the path to sh.
+ */
+ public PathFragment getShExecutable() {
+ return executables.get("sh");
+ }
+
+ /**
+ * Returns a regex-based instrumentation filter instance that used to match label
+ * names to identify targets to be instrumented in the coverage mode.
+ */
+ public RegexFilter getInstrumentationFilter() {
+ return options.instrumentationFilter;
+ }
+
+ /**
+ * Returns the set of labels for coverage.
+ */
+ public Set<Label> getCoverageLabels() {
+ return coverageLabels;
+ }
+
+ /**
+ * Returns the set of labels for the coverage report generator.
+ */
+ public Set<Label> getCoverageReportGeneratorLabels() {
+ return coverageReportGeneratorLabels;
+ }
+
+ /**
+ * Returns true if bazel should show analyses results, even if it did not
+ * re-run the analysis.
+ */
+ public boolean showCachedAnalysisResults() {
+ return options.showCachedAnalysisResults;
+ }
+
+ /**
+ * Returns a new, unordered mapping of names to values of "Make" variables defined by this
+ * configuration.
+ *
+ * <p>This does *not* include package-defined overrides (e.g. vardef)
+ * and so should not be used by the build logic. This is used only for
+ * the 'info' command.
+ *
+ * <p>Command-line definitions of make enviroments override variables defined by
+ * {@code Fragment.addGlobalMakeVariables()}.
+ */
+ public Map<String, String> getMakeEnvironment() {
+ Map<String, String> makeEnvironment = new HashMap<>();
+ makeEnvironment.putAll(globalMakeEnv);
+ for (Fragment fragment : fragments.values()) {
+ makeEnvironment.putAll(fragment.getCommandLineDefines());
+ }
+ return ImmutableMap.copyOf(makeEnvironment);
+ }
+
+ /**
+ * Returns a new, unordered mapping of names that are set through the command lines.
+ * (Fragments, in particular the Google C++ support, can set variables through the
+ * command line.)
+ */
+ public Map<String, String> getCommandLineDefines() {
+ ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
+ for (Fragment fragment : fragments.values()) {
+ builder.putAll(fragment.getCommandLineDefines());
+ }
+ return builder.build();
+ }
+
+ /**
+ * Returns the global defaults for this configuration for the Make environment.
+ */
+ public Map<String, String> getGlobalMakeEnvironment() {
+ return globalMakeEnv;
+ }
+
+ /**
+ * Returns a (key, value) mapping to insert into the subcommand environment for coverage
+ * actions.
+ */
+ public Map<String, String> getCoverageEnvironment() {
+ Map<String, String> env = new HashMap<>();
+ for (Fragment fragment : fragments.values()) {
+ env.putAll(fragment.getCoverageEnvironment());
+ }
+ return env;
+ }
+
+ /**
+ * Returns the default value for the specified "Make" variable for this
+ * configuration. Returns null if no value was found.
+ */
+ public String getMakeVariableDefault(String var) {
+ return globalMakeEnv.get(var);
+ }
+
+ /**
+ * Returns a configuration fragment instances of the given class.
+ */
+ @SkylarkCallable(name = "fragment", doc = "Returns a configuration fragment using the key.")
+ public <T extends Fragment> T getFragment(Class<T> clazz) {
+ return clazz.cast(fragments.get(clazz));
+ }
+
+ /**
+ * Returns true if the requested configuration fragment is present.
+ */
+ public <T extends Fragment> boolean hasFragment(Class<T> clazz) {
+ return getFragment(clazz) != null;
+ }
+
+ /**
+ * Returns true if all requested configuration fragment are present (this may be slow).
+ */
+ public boolean hasAllFragments(Set<Class<?>> fragmentClasses) {
+ for (Class<?> fragmentClass : fragmentClasses) {
+ if (!hasFragment(fragmentClass.asSubclass(Fragment.class))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if non-functional build stamps are enabled.
+ */
+ public boolean stampBinaries() {
+ return options.stampBinaries;
+ }
+
+ /**
+ * Returns true if extended sanity checks should be enabled.
+ */
+ public boolean extendedSanityChecks() {
+ return options.extendedSanityChecks;
+ }
+
+ /**
+ * Returns true if we are building runfiles symlinks for this configuration.
+ */
+ public boolean buildRunfiles() {
+ return options.buildRunfiles;
+ }
+
+ public boolean getCheckFilesetDependenciesRecursively() {
+ return options.checkFilesetDependenciesRecursively;
+ }
+
+ public List<String> getTestArguments() {
+ return options.testArguments;
+ }
+
+ public String getTestFilter() {
+ return options.testFilter;
+ }
+
+ /**
+ * Returns user-specified test environment variables and their values, as
+ * set by the --test_env options.
+ */
+ public Map<String, String> getTestEnv() {
+ return getTestEnv(options.testEnvironment, clientEnvironment);
+ }
+
+ /**
+ * Returns user-specified test environment variables and their values, as
+ * set by the --test_env options.
+ *
+ * @param envOverrides The --test_env flag values.
+ * @param clientEnvironment The full client environment.
+ */
+ public static Map<String, String> getTestEnv(List<Map.Entry<String, String>> envOverrides,
+ Map<String, String> clientEnvironment) {
+ Map<String, String> testEnv = new HashMap<>();
+ for (Map.Entry<String, String> var : envOverrides) {
+ if (var.getValue() != null) {
+ testEnv.put(var.getKey(), var.getValue());
+ } else {
+ String value = clientEnvironment.get(var.getKey());
+ if (value != null) {
+ testEnv.put(var.getKey(), value);
+ }
+ }
+ }
+ return testEnv;
+ }
+
+ public TriState cacheTestResults() {
+ return options.cacheTestResults;
+ }
+
+ public int getMinParamFileSize() {
+ return options.minParamFileSize;
+ }
+
+ @SkylarkCallable(name = "coverage_enabled", structField = true,
+ doc = "A boolean that tells whether code coverage is enabled.")
+ public boolean isCodeCoverageEnabled() {
+ return options.collectCodeCoverage;
+ }
+
+ public boolean isMicroCoverageEnabled() {
+ return options.collectMicroCoverage;
+ }
+
+ public boolean isActionsEnabled() {
+ return actionsEnabled;
+ }
+
+ public TestActionBuilder.TestShardingStrategy testShardingStrategy() {
+ return options.testShardingStrategy;
+ }
+
+ /**
+ * @return number of times the given test should run.
+ * If the test doesn't match any of the filters, runs it once.
+ */
+ public int getRunsPerTestForLabel(Label label) {
+ for (PerLabelOptions perLabelRuns : options.runsPerTest) {
+ if (perLabelRuns.isIncluded(label)) {
+ return Integer.parseInt(Iterables.getOnlyElement(perLabelRuns.getOptions()));
+ }
+ }
+ return 1;
+ }
+
+ public RunUnder getRunUnder() {
+ return options.runUnder;
+ }
+
+ /**
+ * Returns true if this is a host configuration.
+ */
+ public boolean isHostConfiguration() {
+ return options.isHost;
+ }
+
+ public boolean checkVisibility() {
+ return options.checkVisibility;
+ }
+
+ public boolean checkLicenses() {
+ return options.checkLicenses;
+ }
+
+ public boolean enforceConstraints() {
+ return options.enforceConstraints;
+ }
+
+ public List<Label> getActionListeners() {
+ return actionsEnabled ? options.actionListeners : ImmutableList.<Label>of();
+ }
+
+ /**
+ * Returns compilation mode.
+ */
+ public CompilationMode getCompilationMode() {
+ return options.compilationMode;
+ }
+
+ /**
+ * Helper method to create a key from the BuildConfiguration initialization
+ * parameters and any additional component suppliers.
+ */
+ static String computeCacheKey(BlazeDirectories directories,
+ Map<Class<? extends Fragment>, Fragment> fragments,
+ BuildOptions buildOptions, Map<String, String> clientEnv) {
+
+ // Creates a full fingerprint of all constructor parameters, used for
+ // canonicalization.
+ //
+ // Note the use of each Path's FileSystem field; the test suite creates
+ // many paths of equal name but belonging to distinct filesystems, so we
+ // have to detect this. (Note however that we're relying on the
+ // injectiveness of identityHashCode for FileSystem, which is inelegant,
+ // but only affects the tests, since the production code uses only one
+ // instance.)
+
+ ImmutableList.Builder<String> keys = ImmutableList.builder();
+
+ // NOTE: identityHashCode isn't sound; may cause tests to fail.
+ keys.add(String.valueOf(System.identityHashCode(directories.getOutputBase().getFileSystem())));
+ keys.add(directories.getOutputBase().toString());
+ keys.add(buildOptions.computeCacheKey());
+ // This is needed so that if we have --test_env=VAR, the configuration key is updated if the
+ // environment variable VAR is updated.
+ keys.add(BuildConfiguration.getTestEnv(
+ buildOptions.get(Options.class).testEnvironment, clientEnv).toString());
+ keys.add(directories.getWorkspace().toString());
+
+ for (Fragment fragment : fragments.values()) {
+ keys.add(fragment.cacheKey());
+ }
+
+ // TODO(bazel-team): add hash of the FDO/LIPO profile file to config cache key
+
+ return StringUtilities.combineKeys(keys.build());
+ }
+
+ /**
+ * Returns a string that identifies the configuration.
+ *
+ * <p>The string uniquely identifies the configuration. As a result, it can be rather long and
+ * include spaces and other non-alphanumeric characters. If you need a shorter key, use
+ * {@link #shortCacheKey()}.
+ *
+ * @see #computeCacheKey
+ */
+ public final String cacheKey() {
+ return cacheKey;
+ }
+
+ /**
+ * Returns a (relatively) short key that identifies the configuration.
+ *
+ * <p>The short key is the short name of the configuration concatenated with a hash of the
+ * {@link #cacheKey()}.
+ */
+ public final String shortCacheKey() {
+ return shortCacheKey;
+ }
+
+ /** Returns a copy of the build configuration options for this configuration. */
+ public BuildOptions cloneOptions() {
+ return buildOptions.clone();
+ }
+
+ /**
+ * Prepare the fdo support. It reads data into memory that is used during analysis. The analysis
+ * phase is generally not allowed to perform disk I/O. This code is here because it is
+ * conceptually part of the analysis phase, and it needs to happen when the loading phase is
+ * complete.
+ */
+ public void prepareToBuild(Path execRoot, ArtifactFactory artifactFactory,
+ PackageRootResolver resolver) throws ViewCreationFailedException {
+ for (Fragment fragment : fragments.values()) {
+ fragment.prepareHook(execRoot, artifactFactory, getGenfilesFragment(), resolver);
+ }
+ }
+
+ /**
+ * Declares dependencies on any relevant Skyframe values (for example, relevant FileValues).
+ */
+ public void declareSkyframeDependencies(SkyFunction.Environment env) {
+ for (Fragment fragment : fragments.values()) {
+ fragment.declareSkyframeDependencies(env);
+ }
+ }
+
+ /**
+ * Returns all the roots for this configuration.
+ */
+ public List<Root> getRoots() {
+ List<Root> roots = new ArrayList<>();
+
+ // Configuration-specific roots.
+ roots.add(getBinDirectory());
+ roots.add(getGenfilesDirectory());
+ roots.add(getIncludeDirectory());
+ roots.add(getMiddlemanDirectory());
+ roots.add(getTestLogsDirectory());
+
+ // Fragment-defined roots
+ for (Fragment fragment : fragments.values()) {
+ fragment.addRoots(roots);
+ }
+
+ return ImmutableList.copyOf(roots);
+ }
+
+ public ListMultimap<String, Label> getAllLabels() {
+ return buildOptions.getAllLabels();
+ }
+
+ public String getCpu() {
+ return options.cpu;
+ }
+
+ /**
+ * Returns true is incremental builds are supported with this configuration.
+ */
+ public boolean supportsIncrementalBuild() {
+ for (Fragment fragment : fragments.values()) {
+ if (!fragment.supportsIncrementalBuild()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if the configuration performs static linking.
+ */
+ public boolean performsStaticLink() {
+ for (Fragment fragment : fragments.values()) {
+ if (fragment.performsStaticLink()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Deletes temporary directories before execution phase. This is only called for
+ * target configuration.
+ */
+ public void prepareForExecutionPhase() throws IOException {
+ for (Fragment fragment : fragments.values()) {
+ fragment.prepareForExecutionPhase();
+ }
+ }
+
+ /**
+ * Collects executables defined by fragments.
+ */
+ private ImmutableMap<String, PathFragment> collectExecutables() {
+ ImmutableMap.Builder<String, PathFragment> builder = new ImmutableMap.Builder<>();
+ for (Fragment fragment : fragments.values()) {
+ fragment.defineExecutables(builder);
+ }
+ return builder.build();
+ }
+
+ /**
+ * See {@code BuildConfigurationCollection.Transitions.getArtifactOwnerConfiguration()}.
+ */
+ public BuildConfiguration getArtifactOwnerConfiguration() {
+ return transitions.getArtifactOwnerConfiguration();
+ }
+
+ /**
+ * @return whether proto header modules should be built.
+ */
+ public boolean getProtoHeaderModules() {
+ return options.protoHeaderModules;
+ }
+
+ /**
+ * @return the list of default features used for all packages.
+ */
+ public List<String> getDefaultFeatures() {
+ return options.defaultFeatures;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationCollection.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationCollection.java
new file mode 100644
index 0000000..e36a681
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationCollection.java
@@ -0,0 +1,276 @@
+// 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.config;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ListMultimap;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.Attribute.SplitTransition;
+import com.google.devtools.build.lib.packages.Attribute.Transition;
+import com.google.devtools.build.lib.packages.InputFile;
+import com.google.devtools.build.lib.packages.PackageGroup;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.Target;
+
+import java.io.PrintStream;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * The primary container for all main {@link BuildConfiguration} instances,
+ * currently "target", "data", and "host".
+ *
+ * <p>The target configuration is used for all targets specified on the command
+ * line. Data dependencies of targets in the target configuration use the data
+ * configuration instead.
+ *
+ * <p>The host configuration is used for tools that are executed during the
+ * build, e. g, compilers.
+ *
+ * <p>The "related" configurations are also contained in this class.
+ */
+@ThreadSafe
+public final class BuildConfigurationCollection implements Serializable {
+ private final ImmutableList<BuildConfiguration> targetConfigurations;
+
+ public BuildConfigurationCollection(List<BuildConfiguration> targetConfigurations)
+ throws InvalidConfigurationException {
+ this.targetConfigurations = ImmutableList.copyOf(targetConfigurations);
+
+ // Except for the host configuration (which may be identical across target configs), the other
+ // configurations must all have different cache keys or we will end up with problems.
+ HashMap<String, BuildConfiguration> cacheKeyConflictDetector = new HashMap<>();
+ for (BuildConfiguration config : getAllConfigurations()) {
+ if (cacheKeyConflictDetector.containsKey(config.cacheKey())) {
+ throw new InvalidConfigurationException("Conflicting configurations: " + config + " & "
+ + cacheKeyConflictDetector.get(config.cacheKey()));
+ }
+ cacheKeyConflictDetector.put(config.cacheKey(), config);
+ }
+ }
+
+ /**
+ * Creates an empty configuration collection which will return null for everything.
+ */
+ public BuildConfigurationCollection() {
+ this.targetConfigurations = ImmutableList.of();
+ }
+
+ public static BuildConfiguration configureTopLevelTarget(BuildConfiguration topLevelConfiguration,
+ Target toTarget) {
+ if (toTarget instanceof InputFile || toTarget instanceof PackageGroup) {
+ return null;
+ }
+ return topLevelConfiguration.getTransitions().toplevelConfigurationHook(toTarget);
+ }
+
+ public ImmutableList<BuildConfiguration> getTargetConfigurations() {
+ return targetConfigurations;
+ }
+
+ /**
+ * Returns all configurations that can be reached from the target configuration through any kind
+ * of configuration transition.
+ */
+ public Collection<BuildConfiguration> getAllConfigurations() {
+ Set<BuildConfiguration> result = new LinkedHashSet<>();
+ for (BuildConfiguration config : targetConfigurations) {
+ result.addAll(config.getAllReachableConfigurations());
+ }
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof BuildConfigurationCollection)) {
+ return false;
+ }
+ BuildConfigurationCollection that = (BuildConfigurationCollection) obj;
+ return this.targetConfigurations.equals(that.targetConfigurations);
+ }
+
+ @Override
+ public int hashCode() {
+ return targetConfigurations.hashCode();
+ }
+
+ /**
+ * Prints the configuration graph in dot format to the given print stream. This is only intended
+ * for debugging.
+ */
+ public void dumpAsDotGraph(PrintStream out) {
+ out.println("digraph g {");
+ out.println(" ratio = 0.3;");
+ for (BuildConfiguration config : getAllConfigurations()) {
+ String from = config.shortCacheKey();
+ for (Map.Entry<? extends Transition, ConfigurationHolder> entry :
+ config.getTransitions().getTransitionTable().entrySet()) {
+ BuildConfiguration toConfig = entry.getValue().getConfiguration();
+ if (toConfig == config) {
+ continue;
+ }
+ String to = toConfig == null ? "ERROR" : toConfig.shortCacheKey();
+ out.println(" \"" + from + "\" -> \"" + to + "\" [label=\"" + entry.getKey() + "\"]");
+ }
+ }
+ out.println("}");
+ }
+
+ /**
+ * The outgoing transitions for a build configuration.
+ */
+ public static class Transitions implements Serializable {
+ protected final BuildConfiguration configuration;
+
+ /**
+ * Look up table for the configuration transitions, i.e., HOST, DATA, etc.
+ */
+ private final Map<? extends Transition, ConfigurationHolder> transitionTable;
+
+ // TODO(bazel-team): Consider merging transitionTable into this.
+ private final ListMultimap<? super SplitTransition<?>, BuildConfiguration> splitTransitionTable;
+
+ public Transitions(BuildConfiguration configuration,
+ Map<? extends Transition, ConfigurationHolder> transitionTable,
+ ListMultimap<? extends SplitTransition<?>, BuildConfiguration> splitTransitionTable) {
+ this.configuration = configuration;
+ this.transitionTable = ImmutableMap.copyOf(transitionTable);
+ this.splitTransitionTable = ImmutableListMultimap.copyOf(splitTransitionTable);
+ }
+
+ public Transitions(BuildConfiguration configuration,
+ Map<? extends Transition, ConfigurationHolder> transitionTable) {
+ this(configuration, transitionTable,
+ ImmutableListMultimap.<SplitTransition<?>, BuildConfiguration>of());
+ }
+
+ public Map<? extends Transition, ConfigurationHolder> getTransitionTable() {
+ return transitionTable;
+ }
+
+ public ListMultimap<? super SplitTransition<?>, BuildConfiguration> getSplitTransitionTable() {
+ return splitTransitionTable;
+ }
+
+ public List<BuildConfiguration> getSplitConfigurations(SplitTransition<?> transition) {
+ if (splitTransitionTable.containsKey(transition)) {
+ return splitTransitionTable.get(transition);
+ } else {
+ Preconditions.checkState(transition.defaultsToSelf());
+ return ImmutableList.of(configuration);
+ }
+ }
+
+ /**
+ * Adds all configurations that are directly reachable from this configuration through
+ * any kind of configuration transition.
+ */
+ public void addDirectlyReachableConfigurations(Collection<BuildConfiguration> queue) {
+ for (ConfigurationHolder holder : transitionTable.values()) {
+ if (holder.configuration != null) {
+ queue.add(holder.configuration);
+ }
+ }
+ queue.addAll(splitTransitionTable.values());
+ }
+
+ /**
+ * Artifacts need an owner in Skyframe. By default it's the same configuration as what
+ * the configured target has, but it can be overridden if necessary.
+ *
+ * @return the artifact owner configuration
+ */
+ public BuildConfiguration getArtifactOwnerConfiguration() {
+ return configuration;
+ }
+
+ /**
+ * Returns the new configuration after traversing a dependency edge with a
+ * given configuration transition.
+ *
+ * @param configurationTransition the configuration transition
+ * @return the new configuration
+ */
+ public BuildConfiguration getConfiguration(Transition configurationTransition) {
+ ConfigurationHolder holder = transitionTable.get(configurationTransition);
+ if (holder == null && configurationTransition.defaultsToSelf()) {
+ return configuration;
+ }
+ return holder.configuration;
+ }
+
+ /**
+ * Arbitrary configuration transitions can be implemented by overriding this hook.
+ */
+ @SuppressWarnings("unused")
+ public BuildConfiguration configurationHook(Rule fromTarget,
+ Attribute attribute, Target toTarget, BuildConfiguration toConfiguration) {
+ return toConfiguration;
+ }
+
+ /**
+ * Associating configurations to top-level targets can be implemented by overriding this hook.
+ */
+ @SuppressWarnings("unused")
+ public BuildConfiguration toplevelConfigurationHook(Target toTarget) {
+ return configuration;
+ }
+ }
+
+ /**
+ * A holder class for {@link BuildConfiguration} instances that allows {@code null} values,
+ * because none of the Table implementations allow them.
+ */
+ public static final class ConfigurationHolder implements Serializable {
+ private final BuildConfiguration configuration;
+
+ public ConfigurationHolder(BuildConfiguration configuration) {
+ this.configuration = configuration;
+ }
+
+ public BuildConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ @Override
+ public int hashCode() {
+ return configuration == null ? 0 : configuration.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof ConfigurationHolder)) {
+ return false;
+ }
+ return Objects.equals(configuration, ((ConfigurationHolder) o).configuration);
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationKey.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationKey.java
new file mode 100644
index 0000000..e8fcf34
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationKey.java
@@ -0,0 +1,92 @@
+// 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.config;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.ListMultimap;
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.syntax.Label;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A key for the creation of {@link BuildConfigurationCollection} instances.
+ */
+public final class BuildConfigurationKey {
+
+ private final BuildOptions buildOptions;
+ private final BlazeDirectories directories;
+ private final Map<String, String> clientEnv;
+ private final ImmutableSortedSet<String> multiCpu;
+
+ /**
+ * Creates a key for the creation of {@link BuildConfigurationCollection} instances.
+ *
+ * Note that the BuildConfiguration.Options instance must not contain unresolved relative paths.
+ */
+ public BuildConfigurationKey(BuildOptions buildOptions, BlazeDirectories directories,
+ Map<String, String> clientEnv, Set<String> multiCpu) {
+ this.buildOptions = Preconditions.checkNotNull(buildOptions);
+ this.directories = Preconditions.checkNotNull(directories);
+ this.clientEnv = ImmutableMap.copyOf(clientEnv);
+ this.multiCpu = ImmutableSortedSet.copyOf(multiCpu);
+ }
+
+ public BuildConfigurationKey(BuildOptions buildOptions, BlazeDirectories directories,
+ Map<String, String> clientEnv) {
+ this(buildOptions, directories, clientEnv, ImmutableSet.<String>of());
+ }
+
+ public BuildOptions getBuildOptions() {
+ return buildOptions;
+ }
+
+ public BlazeDirectories getDirectories() {
+ return directories;
+ }
+
+ public Map<String, String> getClientEnv() {
+ return clientEnv;
+ }
+
+ public ImmutableSortedSet<String> getMultiCpu() {
+ return multiCpu;
+ }
+
+ public ListMultimap<String, Label> getLabelsToLoadUnconditionally() {
+ return buildOptions.getAllLabels();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof BuildConfigurationKey)) {
+ return false;
+ }
+ BuildConfigurationKey k = (BuildConfigurationKey) o;
+ return buildOptions.equals(k.buildOptions)
+ && directories.equals(k.directories)
+ && clientEnv.equals(k.clientEnv)
+ && multiCpu.equals(k.multiCpu);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(buildOptions, directories, clientEnv, multiCpu);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java
new file mode 100644
index 0000000..afe408f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java
@@ -0,0 +1,254 @@
+// 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.config;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ListMultimap;
+import com.google.devtools.build.lib.packages.Attribute.SplitTransition;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.common.options.Options;
+import com.google.devtools.common.options.OptionsBase;
+import com.google.devtools.common.options.OptionsClassProvider;
+import com.google.devtools.common.options.OptionsParser;
+import com.google.devtools.common.options.OptionsParsingException;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+/**
+ * This is a collection of command-line options from all configuration fragments. Contains
+ * a single instance for all FragmentOptions classes provided by Blaze language modules.
+ */
+public final class BuildOptions implements Cloneable, Serializable {
+ /**
+ * Creates a BuildOptions object with all options set to its default value.
+ */
+ public static BuildOptions createDefaults(Iterable<Class<? extends FragmentOptions>> options) {
+ Builder builder = builder();
+ for (Class<? extends FragmentOptions> optionsClass : options) {
+ builder.add(Options.getDefaults(optionsClass));
+ }
+ return builder.build();
+ }
+
+ /**
+ * This function creates a new BuildOptions instance for host.
+ *
+ * @param fallback if true, we have already tried the user specified hostCpu options
+ * and it didn't work, so now we try the default options instead.
+ */
+ public BuildOptions createHostOptions(boolean fallback) {
+ Builder builder = builder();
+ for (FragmentOptions options : fragmentOptionsMap.values()) {
+ builder.add(options.getHost(fallback));
+ }
+ return builder.build();
+ }
+
+ /**
+ * Returns a list of potential split configuration transitions by calling {@link
+ * FragmentOptions#getPotentialSplitTransitions} on all the fragments.
+ */
+ public List<SplitTransition<BuildOptions>> getPotentialSplitTransitions() {
+ List<SplitTransition<BuildOptions>> result = new ArrayList<>();
+ for (FragmentOptions options : fragmentOptionsMap.values()) {
+ result.addAll(options.getPotentialSplitTransitions());
+ }
+ return result;
+ }
+
+ /**
+ * Creates an BuildOptions class by taking the option values from an options provider
+ * (eg. an OptionsParser).
+ */
+ public static BuildOptions of(List<Class<? extends FragmentOptions>> optionsList,
+ OptionsClassProvider provider) {
+ Builder builder = builder();
+ for (Class<? extends FragmentOptions> optionsClass : optionsList) {
+ builder.add(provider.getOptions(optionsClass));
+ }
+ return builder.build();
+ }
+
+ /**
+ * Creates an BuildOptions class by taking the option values from command-line arguments
+ */
+ @VisibleForTesting
+ public static BuildOptions of(List<Class<? extends FragmentOptions>> optionsList, String... args)
+ throws OptionsParsingException {
+ Builder builder = builder();
+ OptionsParser parser = OptionsParser.newOptionsParser(
+ ImmutableList.<Class<? extends OptionsBase>>copyOf(optionsList));
+ parser.parse(args);
+ for (Class<? extends FragmentOptions> optionsClass : optionsList) {
+ builder.add(parser.getOptions(optionsClass));
+ }
+ return builder.build();
+ }
+
+ /**
+ * Returns the actual instance of a FragmentOptions class.
+ */
+ @SuppressWarnings("unchecked")
+ public <T extends FragmentOptions> T get(Class<T> optionsClass) {
+ FragmentOptions options = fragmentOptionsMap.get(optionsClass);
+ Preconditions.checkNotNull(options);
+ Preconditions.checkArgument(optionsClass.isAssignableFrom(options.getClass()));
+ return (T) options;
+ }
+
+ /**
+ * Returns a multimap of all labels that were specified as options, keyed by the name to be
+ * displayed to the user if something goes wrong. This should be the set of all labels
+ * mentioned in explicit command line options that are not already covered by the
+ * tools/defaults package (see the DefaultsPackage class), and nothing else.
+ */
+ public ListMultimap<String, Label> getAllLabels() {
+ ListMultimap<String, Label> labels = ArrayListMultimap.create();
+ for (FragmentOptions optionsBase : fragmentOptionsMap.values()) {
+ optionsBase.addAllLabels(labels);
+ }
+ return labels;
+ }
+
+ // It would be very convenient to use a Multimap here, but we cannot do that because we need to
+ // support defaults labels that have zero elements.
+ ImmutableMap<String, ImmutableSet<Label>> getDefaultsLabels() {
+ BuildConfiguration.Options opts = get(BuildConfiguration.Options.class);
+ Map<String, Set<Label>> collector = new TreeMap<>();
+ for (FragmentOptions fragment : fragmentOptionsMap.values()) {
+ for (Map.Entry<String, Set<Label>> entry : fragment.getDefaultsLabels(opts).entrySet()) {
+ if (!collector.containsKey(entry.getKey())) {
+ collector.put(entry.getKey(), new TreeSet<Label>());
+ }
+ collector.get(entry.getKey()).addAll(entry.getValue());
+ }
+ }
+
+ ImmutableMap.Builder<String, ImmutableSet<Label>> result = new ImmutableMap.Builder<>();
+ for (Map.Entry<String, Set<Label>> entry : collector.entrySet()) {
+ result.put(entry.getKey(), ImmutableSet.copyOf(entry.getValue()));
+ }
+
+ return result.build();
+ }
+
+ /**
+ * The cache key for the options collection. Recomputes cache key every time it's called.
+ */
+ public String computeCacheKey() {
+ StringBuilder keyBuilder = new StringBuilder();
+ for (FragmentOptions options : fragmentOptionsMap.values()) {
+ keyBuilder.append(options.cacheKey());
+ }
+ return keyBuilder.toString();
+ }
+
+ /**
+ * String representation of build options.
+ */
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (FragmentOptions options : fragmentOptionsMap.values()) {
+ stringBuilder.append(options.toString());
+ }
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Returns the options contained in this collection.
+ */
+ public Iterable<FragmentOptions> getOptions() {
+ return fragmentOptionsMap.values();
+ }
+
+ /**
+ * Creates a copy of the BuildOptions object that contains copies of the FragmentOptions.
+ */
+ @Override
+ public BuildOptions clone() {
+ ImmutableMap.Builder<Class<? extends FragmentOptions>, FragmentOptions> builder =
+ ImmutableMap.builder();
+ for (Map.Entry<Class<? extends FragmentOptions>, FragmentOptions> entry :
+ fragmentOptionsMap.entrySet()) {
+ builder.put(entry.getKey(), entry.getValue().clone());
+ }
+ return new BuildOptions(builder.build());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (this == other) || (other instanceof BuildOptions &&
+ fragmentOptionsMap.equals(((BuildOptions) other).fragmentOptionsMap));
+ }
+
+ @Override
+ public int hashCode() {
+ return fragmentOptionsMap.hashCode();
+ }
+
+ /**
+ * Maps options class definitions to FragmentOptions objects
+ */
+ private final ImmutableMap<Class<? extends FragmentOptions>, FragmentOptions> fragmentOptionsMap;
+
+ private BuildOptions(
+ ImmutableMap<Class<? extends FragmentOptions>, FragmentOptions> fragmentOptionsMap) {
+ this.fragmentOptionsMap = fragmentOptionsMap;
+ }
+
+ /**
+ * Creates a builder object for BuildOptions
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder class for BuildOptions.
+ */
+ public static class Builder {
+ /**
+ * Adds a new FragmentOptions instance to the builder. Overrides previous instances of the
+ * exact same subclass of FragmentOptions.
+ */
+ public <T extends FragmentOptions> Builder add(T options) {
+ builderMap.put(options.getClass(), options);
+ return this;
+ }
+
+ public BuildOptions build() {
+ return new BuildOptions(ImmutableMap.copyOf(builderMap));
+ }
+
+ private Map<Class<? extends FragmentOptions>, FragmentOptions> builderMap;
+
+ private Builder() {
+ builderMap = new HashMap<>();
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/CompilationMode.java b/src/main/java/com/google/devtools/build/lib/analysis/config/CompilationMode.java
new file mode 100644
index 0000000..7f24351
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/CompilationMode.java
@@ -0,0 +1,50 @@
+// 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.config;
+
+import com.google.devtools.common.options.EnumConverter;
+
+/**
+ * This class represents the debug/optimization mode the binaries will be
+ * built for.
+ */
+public enum CompilationMode {
+
+ // Fast build mode (-g0).
+ FASTBUILD("fastbuild"),
+ // Debug mode (-g).
+ DBG("dbg"),
+ // Release mode (-g0 -O2 -DNDEBUG).
+ OPT("opt");
+
+ private final String mode;
+
+ private CompilationMode(String mode) {
+ this.mode = mode;
+ }
+
+ @Override
+ public String toString() {
+ return mode;
+ }
+
+ /**
+ * Converts to {@link CompilationMode}.
+ */
+ public static class Converter extends EnumConverter<CompilationMode> {
+ public Converter() {
+ super(CompilationMode.class, "compilation mode");
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigMatchingProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigMatchingProvider.java
new file mode 100644
index 0000000..c719191
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigMatchingProvider.java
@@ -0,0 +1,54 @@
+// 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.config;
+
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.syntax.Label;
+
+/**
+ * A "configuration target" that asserts whether or not it matches the
+ * configuration it's bound to.
+ *
+ * <p>This can be used, e.g., to declare a BUILD target that defines the
+ * conditions which trigger a configurable attribute branch. In general,
+ * this can be used to trigger for any user-configurable build behavior.
+ */
+@Immutable
+public final class ConfigMatchingProvider implements TransitiveInfoProvider {
+
+ private final Label label;
+ private final boolean matches;
+
+ public ConfigMatchingProvider(Label label, boolean matches) {
+ this.label = label;
+ this.matches = matches;
+ }
+
+ /**
+ * The target's label.
+ */
+ public Label label() {
+ return label;
+ }
+
+ /**
+ * Whether or not the configuration criteria defined by this target match
+ * its actual configuration.
+ */
+ public boolean matches() {
+ return matches;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigRuleClasses.java b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigRuleClasses.java
new file mode 100644
index 0000000..6ee4cce
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigRuleClasses.java
@@ -0,0 +1,204 @@
+// 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.config;
+
+import static com.google.devtools.build.lib.packages.Attribute.attr;
+import static com.google.devtools.build.lib.packages.Type.STRING_DICT;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.analysis.BaseRuleClasses;
+import com.google.devtools.build.lib.analysis.BlazeRule;
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.Type;
+
+/**
+ * Definitions for rule classes that specify or manipulate configuration settings.
+ *
+ * <p>These are not "traditional" rule classes in that they can't be requested as top-level
+ * targets and don't translate input artifacts into output artifacts. Instead, they affect
+ * how *other* rules work. See individual class comments for details.
+ */
+public class ConfigRuleClasses {
+
+ private static final String NONCONFIGURABLE_ATTRIBUTE_REASON =
+ "part of a rule class that *triggers* configurable behavior";
+
+ /**
+ * Common settings for all configurability rules.
+ */
+ @BlazeRule(name = "$config_base_rule",
+ type = RuleClass.Builder.RuleClassType.ABSTRACT,
+ ancestors = { BaseRuleClasses.BaseRule.class })
+ public static final class ConfigBaseRule implements RuleDefinition {
+ @Override
+ public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) {
+ return builder
+ .override(attr("tags", Type.STRING_LIST)
+ // No need to show up in ":all", etc. target patterns.
+ .value(ImmutableList.of("manual"))
+ .nonconfigurable(NONCONFIGURABLE_ATTRIBUTE_REASON))
+ .build();
+ }
+ }
+
+ /**
+ * A named "partial configuration setting" that specifies a set of command-line
+ * "flag=value" bindings.
+ *
+ * <p>For example:
+ * <pre>
+ * config_setting(
+ * name = 'foo',
+ * values = {
+ * 'flag1': 'aValue'
+ * 'flag2': 'bValue'
+ * })
+ * </pre>
+ *
+ * <p>declares a setting that binds command-line flag <pre>flag1</pre> to value
+ * <pre>aValue</pre> and <pre>flag2</pre> to <pre>bValue</pre>.
+ *
+ * <p>This is used by configurable attributes to determine which branch to
+ * follow based on which <pre>config_setting</pre> instance matches all its
+ * flag values in the configurable attribute owner's configuration.
+ *
+ * <p>This rule isn't accessed through the standard {@link RuleContext#getPrerequisites}
+ * interface. This is because Bazel constructs a rule's configured attribute map *before*
+ * its {@link RuleContext} is created (in fact, the map is an input to the context's
+ * constructor). And the config_settings referenced by the rule's configurable attributes are
+ * themselves inputs to that map. So Bazel has special logic to read and properly apply
+ * config_setting instances. See {@link ConfiguredTargetFunction#getConfigConditions} for details.
+ */
+ @BlazeRule(name = "config_setting",
+ type = RuleClass.Builder.RuleClassType.NORMAL,
+ ancestors = { ConfigBaseRule.class },
+ factoryClass = ConfigSetting.class)
+ public static final class ConfigSettingRule implements RuleDefinition {
+ /**
+ * The name of the attribute that declares flag bindings.
+ */
+ public static final String SETTINGS_ATTRIBUTE = "values";
+
+ @Override
+ public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) {
+ return builder
+ /* <!-- #BLAZE_RULE(config_setting).ATTRIBUTE(values) -->
+ The set of configuration values that match this rule (expressed as Blaze flags)
+
+ <i>(Dictionary mapping flags to expected values, both expressed as strings;
+ mandatory)</i>
+
+ <p>This rule inherits the configuration of the configured target that
+ references it in a <code>select</code> statement. It is considered to
+ "match" a Blaze invocation if, for every entry in the dictionary, its
+ configuration matches the entry's expected value. For example
+ <code>values = {"compilation_mode": "opt"}</code> matches the invocations
+ <code>blaze build --compilation_mode=opt ...</code> and
+ <code>blaze build -c opt ...</code> on target-configured rules.
+ </p>
+
+ <p>For convenience's sake, configuration values are specified as Blaze flags (without
+ the preceding <code>"--"</code>). But keep in mind that the two are not the same. This
+ is because targets can be built in multiple configurations within the same
+ build. For example, a host configuration's "cpu" matches the value of
+ <code>--host_cpu</code>, not <code>--cpu</code>. So different instances of the
+ same <code>config_setting</code> may match the same invocation differently
+ depending on the configuration of the rule using them.
+ </p>
+
+ <p>If a flag is not explicitly set at the command line, its default value is used.
+ If a key appears multiple times in the dictionary, only the last instance is used.
+ If a key references a flag that can be set multiple times on the command line (e.g.
+ <code>blaze build --copt=foo --copt=bar --copt=baz ...</code>), a match occurs if
+ *any* of those settings match.
+ <p>
+
+ <p>This attribute cannot be empty.
+ </p>
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr(SETTINGS_ATTRIBUTE, STRING_DICT).mandatory()
+ .nonconfigurable(NONCONFIGURABLE_ATTRIBUTE_REASON))
+ .build();
+ }
+ }
+
+/*<!-- #BLAZE_RULE (NAME = config_setting, TYPE = OTHER, FAMILY = General)[GENERIC_RULE] -->
+
+${ATTRIBUTE_SIGNATURE}
+
+<p>
+ Matches an expected configuration state (expressed as Blaze flags) for the purpose of triggering
+ configurable attributes. See <a href="#select">select</a> for how to consume this rule and
+ <a href="#configurable-attributes">Configurable attributes</a> for an overview of
+ the general feature.
+
+${ATTRIBUTE_DEFINITION}
+
+<h4 id="config_setting_examples">Examples</h4>
+
+<p>The following matches any Blaze invocation that specifies <code>--compilation_mode=opt</code>
+ or <code>-c opt</code> (either explicitly at the command line or implicitly from .blazerc
+ files, etc.), when applied to a target configuration rule:
+</p>
+
+<pre class="code">
+config_setting(
+ name = "simple",
+ values = {"compilation_mode": "opt"}
+)
+</pre>
+
+<p>The following matches any Blaze invocation that builds for ARM and applies a custom define
+ (e.g. <code>blaze build --cpu=armeabi --define FOO=bar ...</code>), , when applied to a target
+ configuration rule:
+</p>
+
+<pre class="code">
+config_setting(
+ name = "two_conditions",
+ values = {
+ "cpu": "armeabi",
+ "define": "FOO=bar"
+ }
+)
+</pre>
+
+<h4 id="config_setting_notes">Notes</h4>
+
+<p>See <a href="#select">select</a> for policies on what happens depending on how many rules match
+ an invocation.
+</p>
+
+<p>For flags that support shorthand forms (e.g. <code>--compilation_mode</code> vs.
+ <code>-c</code>), <code>values</code> definitions must use the full form. These automatically
+ match invocations using either form.
+</p>
+
+<p>The currently endorsed method for creating custom conditions that can't be expressed through
+ dedicated build flags is through the --define flag. Use this flag with caution: it's not ideal
+ and only endorsed for lack of a currently better workaround. See the
+ <a href="#configurable-attributes">Configurable attributes</a> section for more discussion.
+</p>
+
+<p>Try to consolidate <code>config_setting</code> definitions as much as possible. In other words,
+ define <code>//common/conditions:foo</code> in one common package instead of repeating separate
+ instances in <code>//project1:foo</code>, <code>//project2:foo</code>, etc. that all mean the
+ same thing.
+</p>
+
+<!-- #END_BLAZE_RULE -->*/
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigSetting.java b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigSetting.java
new file mode 100644
index 0000000..97ad4ff
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigSetting.java
@@ -0,0 +1,170 @@
+// 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.config;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.FileProvider;
+import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.RunfilesProvider;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+import com.google.devtools.common.options.OptionsBase;
+import com.google.devtools.common.options.OptionsParser;
+import com.google.devtools.common.options.OptionsParsingException;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation for the config_setting rule.
+ *
+ * <p>This is a "pseudo-rule" in that its purpose isn't to generate output artifacts
+ * from input artifacts. Rather, it provides configuration context to rules that
+ * depend on it.
+ */
+public class ConfigSetting implements RuleConfiguredTargetFactory {
+
+ @Override
+ public ConfiguredTarget create(RuleContext ruleContext) throws InterruptedException {
+ // Get the required flag=value settings for this rule.
+ Map<String, String> settings = NonconfigurableAttributeMapper.of(ruleContext.getRule())
+ .get(ConfigRuleClasses.ConfigSettingRule.SETTINGS_ATTRIBUTE, Type.STRING_DICT);
+ if (settings.isEmpty()) {
+ ruleContext.attributeError(ConfigRuleClasses.ConfigSettingRule.SETTINGS_ATTRIBUTE,
+ "no settings specified");
+ return null;
+ }
+
+ ConfigMatchingProvider configMatcher;
+ try {
+ configMatcher = new ConfigMatchingProvider(ruleContext.getLabel(),
+ matchesConfig(settings, ruleContext.getConfiguration()));
+ } catch (OptionsParsingException e) {
+ ruleContext.attributeError(ConfigRuleClasses.ConfigSettingRule.SETTINGS_ATTRIBUTE,
+ "error while parsing configuration settings: " + e.getMessage());
+ return null;
+ }
+
+ return new RuleConfiguredTargetBuilder(ruleContext)
+ .add(RunfilesProvider.class, RunfilesProvider.EMPTY)
+ .add(FileProvider.class, new FileProvider(ruleContext.getLabel(),
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER)))
+ .add(FilesToRunProvider.class, new FilesToRunProvider(ruleContext.getLabel(),
+ ImmutableList.<Artifact>of(), null, null))
+ .add(ConfigMatchingProvider.class, configMatcher)
+ .build();
+ }
+
+ /**
+ * Given a list of [flagName, flagValue] pairs, returns true if flagName == flagValue for
+ * every item in the list under this configuration, false otherwise.
+ */
+ private boolean matchesConfig(Map<String, String> expectedSettings, BuildConfiguration config)
+ throws OptionsParsingException {
+ // Rather than returning fast when we find a mismatch, continue looking at the other flags
+ // to check that they're indeed valid flag specifications.
+ boolean foundMismatch = false;
+
+ // Since OptionsParser instantiation involves reflection, let's try to minimize that happening.
+ Map<Class<? extends OptionsBase>, OptionsParser> parserCache = new HashMap<>();
+
+ for (Map.Entry<String, String> setting : expectedSettings.entrySet()) {
+ String optionName = setting.getKey();
+ String expectedRawValue = setting.getValue();
+
+ Class<? extends OptionsBase> optionClass = config.getOptionClass(optionName);
+ if (optionClass == null) {
+ throw new OptionsParsingException("unknown option: '" + optionName + "'");
+ }
+
+ OptionsParser parser = parserCache.get(optionClass);
+ if (parser == null) {
+ parser = OptionsParser.newOptionsParser(optionClass);
+ parserCache.put(optionClass, parser);
+ }
+ parser.parse("--" + optionName + "=" + expectedRawValue);
+ Object expectedParsedValue = parser.getOptions(optionClass).asMap().get(optionName);
+
+ if (!optionMatches(config, optionName, expectedParsedValue)) {
+ foundMismatch = true;
+ }
+ }
+ return !foundMismatch;
+ }
+
+ /**
+ * For single-value options, returns true iff the option's value matches the expected value.
+ *
+ * <p>For multi-value List options, returns true iff any of the option's values matches
+ * the expected value. This means, e.g. "--tool_tag=foo --tool_tag=bar" would match the
+ * expected condition { 'tool_tag': 'bar' }.
+ *
+ * <p>For multi-value Map options, returns true iff the last instance with the same key as the
+ * expected key has the same value. This means, e.g. "--define foo=1 --define bar=2" would
+ * match { 'define': 'foo=1' }, but "--define foo=1 --define bar=2 --define foo=3" would not
+ * match. Note that the definition of --define states that the last instance takes precedence.
+ */
+ private static boolean optionMatches(BuildConfiguration config, String optionName,
+ Object expectedValue) {
+ Object actualValue = config.getOptionValue(optionName);
+ if (actualValue == null) {
+ return expectedValue == null;
+
+ // Single-value case:
+ } else if (!config.allowsMultipleValues(optionName)) {
+ return actualValue.equals(expectedValue);
+ }
+
+ // Multi-value case:
+ Preconditions.checkState(actualValue instanceof List);
+ Preconditions.checkState(expectedValue instanceof List);
+ List<?> actualList = (List<?>) actualValue;
+ List<?> expectedList = (List<?>) expectedValue;
+
+ if (actualList.isEmpty() || expectedList.isEmpty()) {
+ return actualList.isEmpty() && expectedList.isEmpty();
+ }
+
+ // We're expecting a single value of a multi-value type: the options parser still embeds
+ // that single value within a List container. Retrieve it here.
+ Object expectedSingleValue = Iterables.getOnlyElement(expectedList);
+
+ // Multi-value map:
+ if (actualList.get(0) instanceof Map.Entry) {
+ Map.Entry<?, ?> expectedEntry = (Map.Entry<?, ?>) expectedSingleValue;
+ for (Map.Entry<?, ?> actualEntry : Lists.reverse((List<Map.Entry<?, ?>>) actualList)) {
+ if (actualEntry.getKey().equals(expectedEntry.getKey())) {
+ // Found a key match!
+ return actualEntry.getValue().equals(expectedEntry.getValue());
+ }
+ }
+ return false; // Never found any matching key.
+ }
+
+ // Multi-value list:
+ return actualList.contains(expectedSingleValue);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationEnvironment.java b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationEnvironment.java
new file mode 100644
index 0000000..89722b5
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationEnvironment.java
@@ -0,0 +1,96 @@
+// 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.config;
+
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment;
+import com.google.devtools.build.lib.packages.NoSuchPackageException;
+import com.google.devtools.build.lib.packages.NoSuchTargetException;
+import com.google.devtools.build.lib.packages.Package;
+import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.pkgcache.LoadedPackageProvider;
+import com.google.devtools.build.lib.pkgcache.PackageProvider;
+import com.google.devtools.build.lib.pkgcache.TargetProvider;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.vfs.Path;
+
+import javax.annotation.Nullable;
+
+/**
+ * An environment to support creating BuildConfiguration instances in a hermetic fashion; all
+ * accesses to packages or the file system <b>must</b> go through this interface, so that they can
+ * be recorded for correct caching.
+ */
+public interface ConfigurationEnvironment {
+
+ /**
+ * Returns a target for the given label, loading it if necessary, and throwing an exception if it
+ * does not exist.
+ *
+ * @see TargetProvider#getTarget
+ */
+ Target getTarget(Label label) throws NoSuchPackageException, NoSuchTargetException;
+
+ /** Returns a path for the given file within the given package. */
+ Path getPath(Package pkg, String fileName);
+
+ /** Returns fragment based on fragment class and build options. */
+ <T extends Fragment> T getFragment(BuildOptions buildOptions, Class<T> fragmentType)
+ throws InvalidConfigurationException;
+
+ /** Returns global value of BlazeDirectories. */
+ @Nullable
+ BlazeDirectories getBlazeDirectories();
+
+ /**
+ * An implementation backed by a {@link PackageProvider} instance.
+ */
+ public static final class TargetProviderEnvironment implements ConfigurationEnvironment {
+
+ private final LoadedPackageProvider loadedPackageProvider;
+ private final BlazeDirectories blazeDirectories;
+
+ public TargetProviderEnvironment(LoadedPackageProvider loadedPackageProvider,
+ BlazeDirectories blazeDirectories) {
+ this.loadedPackageProvider = loadedPackageProvider;
+ this.blazeDirectories = blazeDirectories;
+ }
+
+ public TargetProviderEnvironment(LoadedPackageProvider loadedPackageProvider) {
+ this.loadedPackageProvider = loadedPackageProvider;
+ this.blazeDirectories = null;
+ }
+
+ @Override
+ public Target getTarget(Label label) throws NoSuchPackageException, NoSuchTargetException {
+ return loadedPackageProvider.getLoadedTarget(label);
+ }
+
+ @Override
+ public Path getPath(Package pkg, String fileName) {
+ return pkg.getPackageDirectory().getRelative(fileName);
+ }
+
+ @Override
+ public <T extends Fragment> T getFragment(BuildOptions buildOptions, Class<T> fragmentType) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public BlazeDirectories getBlazeDirectories() {
+ return blazeDirectories;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationFactory.java
new file mode 100644
index 0000000..13afb2a
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationFactory.java
@@ -0,0 +1,145 @@
+// 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.config;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.analysis.ConfigurationCollectionFactory;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
+import com.google.devtools.build.lib.events.EventHandler;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/**
+ * A factory class for {@link BuildConfiguration} instances. This is unfortunately more complex,
+ * and should be simplified in the future, if
+ * possible. Right now, creating a {@link BuildConfiguration} instance involves
+ * creating the instance itself and the related configurations; the main method
+ * is {@link #createConfiguration}.
+ *
+ * <p>Avoid calling into this class, and instead use the skyframe infrastructure to obtain
+ * configuration instances.
+ *
+ * <p>Blaze currently relies on the fact that all {@link BuildConfiguration}
+ * instances used in a build can be constructed ahead of time by this class.
+ */
+@ThreadCompatible // safe as long as separate instances are used
+public final class ConfigurationFactory {
+ private final List<ConfigurationFragmentFactory> configurationFragmentFactories;
+ private final ConfigurationCollectionFactory configurationCollectionFactory;
+
+ // A cache of key to configuration instances.
+ private final Cache<String, BuildConfiguration> hostConfigCache =
+ CacheBuilder.newBuilder().softValues().build();
+
+ private boolean performSanityCheck = true;
+
+ public ConfigurationFactory(
+ ConfigurationCollectionFactory configurationCollectionFactory,
+ List<ConfigurationFragmentFactory> fragmentFactories) {
+ this.configurationCollectionFactory =
+ Preconditions.checkNotNull(configurationCollectionFactory);
+ this.configurationFragmentFactories = fragmentFactories;
+ }
+
+ @VisibleForTesting
+ public void forbidSanityCheck() {
+ performSanityCheck = false;
+ }
+
+ /** Create the build configurations with the given options. */
+ @Nullable
+ public BuildConfiguration createConfiguration(
+ PackageProviderForConfigurations loadedPackageProvider, BuildOptions buildOptions,
+ BuildConfigurationKey key, EventHandler errorEventListener)
+ throws InvalidConfigurationException {
+ return configurationCollectionFactory.createConfigurations(this,
+ loadedPackageProvider, buildOptions, key.getClientEnv(),
+ errorEventListener, performSanityCheck);
+ }
+
+ /**
+ * Returns a (possibly new) canonical host BuildConfiguration instance based
+ * upon a given request configuration
+ */
+ @Nullable
+ public BuildConfiguration getHostConfiguration(
+ PackageProviderForConfigurations loadedPackageProvider, Map<String, String> clientEnv,
+ BuildOptions buildOptions, boolean fallback) throws InvalidConfigurationException {
+ return getConfiguration(loadedPackageProvider, buildOptions.createHostOptions(fallback),
+ clientEnv, false, hostConfigCache);
+ }
+
+ /**
+ * The core of BuildConfiguration creation. All host and target instances are
+ * constructed and cached here.
+ */
+ @Nullable
+ public BuildConfiguration getConfiguration(PackageProviderForConfigurations loadedPackageProvider,
+ BuildOptions buildOptions, Map<String, String> clientEnv,
+ boolean actionsDisabled, Cache<String, BuildConfiguration> cache)
+ throws InvalidConfigurationException {
+
+ Map<Class<? extends Fragment>, Fragment> fragments = new HashMap<>();
+ // Create configuration fragments
+ for (ConfigurationFragmentFactory factory : configurationFragmentFactories) {
+ Class<? extends Fragment> fragmentType = factory.creates();
+ Fragment fragment = loadedPackageProvider.getFragment(buildOptions, fragmentType);
+ if (fragment != null && fragments.get(fragment) == null) {
+ fragments.put(fragment.getClass(), fragment);
+ }
+ }
+ BlazeDirectories directories = loadedPackageProvider.getDirectories();
+ if (loadedPackageProvider.valuesMissing()) {
+ return null;
+ }
+
+ // Sort the fragments by class name to make sure that the order is stable. Afterwards, copy to
+ // an ImmutableMap, which keeps the order stable, but uses hashing, and drops the reference to
+ // the Comparator object.
+ fragments = ImmutableSortedMap.copyOf(fragments, new Comparator<Class<? extends Fragment>>() {
+ @Override
+ public int compare(Class<? extends Fragment> o1, Class<? extends Fragment> o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+ });
+ fragments = ImmutableMap.copyOf(fragments);
+
+ String key = BuildConfiguration.computeCacheKey(
+ directories, fragments, buildOptions, clientEnv);
+ BuildConfiguration configuration = cache.getIfPresent(key);
+ if (configuration == null) {
+ configuration = new BuildConfiguration(directories, fragments, buildOptions,
+ clientEnv, actionsDisabled);
+ cache.put(key, configuration);
+ }
+ return configuration;
+ }
+
+ public List<ConfigurationFragmentFactory> getFactories() {
+ return configurationFragmentFactories;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationFragmentFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationFragmentFactory.java
new file mode 100644
index 0000000..8ca8f1c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/ConfigurationFragmentFactory.java
@@ -0,0 +1,39 @@
+// 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.config;
+
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment;
+
+import javax.annotation.Nullable;
+
+/**
+ * A factory that creates configuration fragments.
+ */
+public interface ConfigurationFragmentFactory {
+ /**
+ * Creates a configuration fragment.
+ *
+ * @param env the ConfigurationEnvironment for querying targets and paths
+ * @param buildOptions command-line options (see {@link FragmentOptions})
+ * @return the configuration fragment or null if some required dependencies are missing.
+ */
+ @Nullable
+ BuildConfiguration.Fragment create(ConfigurationEnvironment env, BuildOptions buildOptions)
+ throws InvalidConfigurationException;
+
+ /**
+ * @return the exact type of the fragment this factory creates.
+ */
+ Class<? extends Fragment> creates();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/DefaultsPackage.java b/src/main/java/com/google/devtools/build/lib/analysis/config/DefaultsPackage.java
new file mode 100644
index 0000000..207d49a
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/DefaultsPackage.java
@@ -0,0 +1,164 @@
+// 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.config;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.Label.SyntaxException;
+
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A helper class to compute and inject a defaults package into the package cache.
+ *
+ * <p>The <code>//tools/defaults</code> package provides a mechanism let tool locations be
+ * specified over the commandline, without requiring any special support in the rule code.
+ * As such, it can be used in genrule <code>$(location)</code> substitutions.
+ *
+ * <p>It works as follows:
+ * <ul>
+ *
+ * <li> SomeLanguage.createCompileAction will refer to a host-configured target for the
+ * compiler by looking for
+ * <code>env.getHostPrerequisiteArtifact("$somelanguage_compiler")</code>.
+ *
+ * <li> the attribute <code>$somelanguage_compiler</code> is defined in the
+ * {@link RuleDefinition} subclass for that language.
+ *
+ * <li> if the attribute cannot be set on the command-line, its value may be a normal label.
+ *
+ * <li> if the attribute can be set on the command-line, its value will be
+ * <code>//tools/defaults:somelanguage_compiler</code>.
+ *
+ * <li> in the latter case, the {@link BuildConfiguration.Fragment} subclass will define the
+ * option (with an existing target, eg. <code>//third_party/somelanguage:compiler</code>), and
+ * return the name in its implementation of {@link FragmentOptions#getDefaultsLabels}.
+ *
+ * <li> On startup, the rule is wired up with <code>//tools/defaults:somelanguage_compiler</code>.
+ *
+ * <li> On starting a build, the <code>//tools/defaults</code> package is synthesized, using
+ * the values as specified on the command-line. The contents of
+ * <code>tools/defaults/BUILD</code> is ignored.
+ *
+ * <li> Hence, changes in the command line values for tools are now handled exactly as if they
+ * were changes in a BUILD file.
+ *
+ * <li> The file <code>tools/defaults/BUILD</code> must exist, so we create a package in that
+ * location.
+ *
+ * <li> The code in {@link DefaultsPackage} can dump the synthesized package as a BUILD file,
+ * so external tooling does not need to understand the intricacies of handling command-line
+ * options.
+ *
+ * </ul>
+ *
+ * <p>For built-in rules (as opposed to genrules), late-bound labels provide an alternative
+ * method of depending on command-line values. These work by declaring attribute default values
+ * to be {@link LateBoundLabel} instances, whose <code>getDefault(Rule rule, T
+ * configuration)</code> method will have access to {@link BuildConfiguration}, which in turn
+ * may depend on command line flag values.
+ */
+public final class DefaultsPackage {
+
+ // The template contents are broken into lines such that the resulting file has no more than 80
+ // characters per line.
+ private static final String HEADER = ""
+ + "# DO NOT EDIT THIS FILE!\n"
+ + "#\n"
+ + "# Bazel does not read this file. Instead, it internally replaces the targets in\n"
+ + "# this package with the correct packages as given on the command line.\n"
+ + "#\n"
+ + "# If these options are not given on the command line, Bazel will use the exact\n"
+ + "# same targets as given here."
+ + "\n"
+ + "package(default_visibility = ['//visibility:public'])\n";
+
+ /**
+ * The map from entries to their values.
+ */
+ private ImmutableMap<String, ImmutableSet<Label>> values;
+
+ private DefaultsPackage(BuildOptions buildOptions) {
+ values = buildOptions.getDefaultsLabels();
+ }
+
+ private String labelsToString(Set<Label> labels) {
+ StringBuffer result = new StringBuffer();
+ for (Label label : labels) {
+ if (result.length() != 0) {
+ result.append(", ");
+ }
+ result.append("'").append(label).append("'");
+ }
+ return result.toString();
+ }
+
+ /**
+ * Returns a string of the defaults package with the given settings.
+ */
+ private String getContent() {
+ Preconditions.checkState(!values.isEmpty());
+ StringBuilder result = new StringBuilder(HEADER);
+ for (Map.Entry<String, ImmutableSet<Label>> entry : values.entrySet()) {
+ result
+ .append("filegroup(name = '")
+ .append(entry.getKey().toLowerCase(Locale.US)).append("',\n")
+ .append(" srcs = [")
+ .append(labelsToString(entry.getValue())).append("])\n");
+ }
+ return result.toString();
+ }
+
+ /**
+ * Returns the defaults package for the default settings.
+ */
+ public static String getDefaultsPackageContent(
+ Iterable<Class<? extends FragmentOptions>> options) {
+ return getDefaultsPackageContent(BuildOptions.createDefaults(options));
+ }
+
+ /**
+ * Returns the defaults package for the given options.
+ */
+ public static String getDefaultsPackageContent(BuildOptions buildOptions) {
+ return new DefaultsPackage(buildOptions).getContent();
+ }
+
+ public static void parseAndAdd(Set<Label> labels, String optionalLabel) {
+ if (optionalLabel != null) {
+ Label label = parseOptionalLabel(optionalLabel);
+ if (label != null) {
+ labels.add(label);
+ }
+ }
+ }
+
+ public static Label parseOptionalLabel(String value) {
+ if (value.startsWith("//")) {
+ try {
+ return Label.parseAbsolute(value);
+ } catch (SyntaxException e) {
+ // We ignore this exception here - it will cause an error message at a later time.
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/FragmentOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/config/FragmentOptions.java
new file mode 100644
index 0000000..ce4b2d2
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/FragmentOptions.java
@@ -0,0 +1,115 @@
+// 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.config;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Multimap;
+import com.google.devtools.build.lib.packages.Attribute.SplitTransition;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.Label.SyntaxException;
+import com.google.devtools.common.options.Options;
+import com.google.devtools.common.options.OptionsBase;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Command-line build options for a Blaze module.
+ */
+public abstract class FragmentOptions extends OptionsBase implements Cloneable, Serializable {
+
+ /**
+ * Adds all labels defined by the options to a multimap. See {@code BuildOptions.getAllLabels()}.
+ *
+ * <p>There should generally be no code duplication between this code and DefaultsPackage. Either
+ * the labels are loaded unconditionally using this method, or they are added as magic labels
+ * using the tools/defaults package, but not both.
+ *
+ * @param labelMap a mutable multimap to which the labels of this fragment should be added
+ */
+ public void addAllLabels(Multimap<String, Label> labelMap) {
+ }
+
+ /**
+ * Returns the labels contributed to the defaults package by this fragment.
+ *
+ * <p>The set of keys returned by this function should be constant, however, the values are
+ * allowed to change depending on the value of the options.
+ */
+ @SuppressWarnings("unused")
+ public Map<String, Set<Label>> getDefaultsLabels(BuildConfiguration.Options commonOptions) {
+ return ImmutableMap.of();
+ }
+
+ /**
+ * Returns a list of potential split configuration transitions for this fragment. Split
+ * configurations usually need to be explicitly enabled by passing in an option.
+ */
+ public List<SplitTransition<BuildOptions>> getPotentialSplitTransitions() {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public FragmentOptions clone() {
+ try {
+ return (FragmentOptions) super.clone();
+ } catch (CloneNotSupportedException e) {
+ // This can't happen.
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Creates a new FragmentOptions instance with all flags set to default.
+ */
+ public FragmentOptions getDefault() {
+ return Options.getDefaults(getClass());
+ }
+
+ /**
+ * Creates a new FragmentOptions instance with flags adjusted to host platform.
+ *
+ * @param fallback see {@code BuildOptions.createHostOptions}
+ */
+ @SuppressWarnings("unused")
+ public FragmentOptions getHost(boolean fallback) {
+ return getDefault();
+ }
+
+ protected void addOptionalLabel(Multimap<String, Label> map, String key, String value) {
+ Label label = parseOptionalLabel(value);
+ if (label != null) {
+ map.put(key, label);
+ }
+ }
+
+ private static Label parseOptionalLabel(String value) {
+ if ((value != null) && value.startsWith("//")) {
+ try {
+ return Label.parseAbsolute(value);
+ } catch (SyntaxException e) {
+ // We ignore this exception here - it will cause an error message at a later time.
+ // TODO(bazel-team): We can use a Converter to check the validity of the crosstoolTop
+ // earlier.
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/InvalidConfigurationException.java b/src/main/java/com/google/devtools/build/lib/analysis/config/InvalidConfigurationException.java
new file mode 100644
index 0000000..c39325d
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/InvalidConfigurationException.java
@@ -0,0 +1,33 @@
+// 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.config;
+
+/**
+ * Thrown if the configuration options lead to an invalid configuration, or if any of the
+ * configuration labels cannot be loaded.
+ */
+public class InvalidConfigurationException extends Exception {
+
+ public InvalidConfigurationException(String message) {
+ super(message);
+ }
+
+ public InvalidConfigurationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public InvalidConfigurationException(Throwable cause) {
+ this(cause.getMessage(), cause);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/PackageProviderForConfigurations.java b/src/main/java/com/google/devtools/build/lib/analysis/config/PackageProviderForConfigurations.java
new file mode 100644
index 0000000..83b4715
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/PackageProviderForConfigurations.java
@@ -0,0 +1,49 @@
+// 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.config;
+
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment;
+import com.google.devtools.build.lib.packages.Package;
+import com.google.devtools.build.lib.pkgcache.LoadedPackageProvider;
+import com.google.devtools.build.lib.syntax.Label.SyntaxException;
+
+import java.io.IOException;
+
+/**
+ * Extended LoadedPackageProvider which is used during a creation of BuildConfiguration.Fragments.
+ */
+public interface PackageProviderForConfigurations extends LoadedPackageProvider {
+ /**
+ * Adds dependency to fileName if needed. Used only in skyframe, for creating correct dependencies
+ * for {@link com.google.devtools.build.lib.skyframe.ConfigurationCollectionValue}.
+ */
+ void addDependency(Package pkg, String fileName) throws SyntaxException, IOException;
+
+ /**
+ * Returns fragment based on fragment type and build options.
+ */
+ <T extends Fragment> T getFragment(BuildOptions buildOptions, Class<T> fragmentType)
+ throws InvalidConfigurationException;
+
+ /**
+ * Returns blaze directories and adds dependency to that value.
+ */
+ BlazeDirectories getDirectories();
+
+ /**
+ * Returns true if any dependency is missing (value of some node hasn't been evaluated yet).
+ */
+ boolean valuesMissing();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/PerLabelOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/config/PerLabelOptions.java
new file mode 100644
index 0000000..1e921e5
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/PerLabelOptions.java
@@ -0,0 +1,128 @@
+// 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.config;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.util.RegexFilter;
+import com.google.devtools.build.lib.util.RegexFilter.RegexFilterConverter;
+import com.google.devtools.common.options.Converter;
+import com.google.devtools.common.options.OptionsParsingException;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Models options that can be added to a command line when a label matches a
+ * given {@link RegexFilter}.
+ */
+public class PerLabelOptions implements Serializable {
+
+ /** The filter used to match labels */
+ private final RegexFilter regexFilter;
+
+ /** The list of options to add when the filter matches a label */
+ private final List<String> optionsList;
+
+ /**
+ * Converts a String to a {@link PerLabelOptions} object. The syntax of the
+ * string is {@code regex_filter@option_1,option_2,...,option_n}. Where
+ * regex_filter stands for the String representation of a {@link RegexFilter},
+ * and {@code option_1} to {@code option_n} stand for arbitrary command line
+ * options. If an option contains a comma it has to be quoted with a
+ * backslash. Options can contain @. Only the first @ is used to split the
+ * string.
+ */
+ public static class PerLabelOptionsConverter implements Converter<PerLabelOptions> {
+
+ @Override
+ public PerLabelOptions convert(String input) throws OptionsParsingException {
+ int atIndex = input.indexOf('@');
+ RegexFilterConverter converter = new RegexFilter.RegexFilterConverter();
+ if (atIndex < 0) {
+ return new PerLabelOptions(converter.convert(input), ImmutableList.<String> of());
+ } else {
+ String filterPiece = input.substring(0, atIndex);
+ String optionsPiece = input.substring(atIndex + 1);
+ List<String> optionsList = new ArrayList<>();
+ for (String option : optionsPiece.split("(?<!\\\\),")) { // Split on ',' but not on '\,'
+ if (option != null && !option.trim().equals("")) {
+ optionsList.add(option.replace("\\,", ","));
+ }
+ }
+ return new PerLabelOptions(converter.convert(filterPiece), optionsList);
+ }
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "a comma-separated list of regex expressions with prefix '-' specifying"
+ + " excluded paths followed by an @ and a comma separated list of options";
+ }
+ }
+
+ public PerLabelOptions(RegexFilter regexFilter, List<String> optionsList) {
+ this.regexFilter = regexFilter;
+ this.optionsList = optionsList;
+ }
+
+ /**
+ * @return true if the given label is matched by the {@link RegexFilter}.
+ */
+ public boolean isIncluded(Label label) {
+ return regexFilter.isIncluded(label.toString());
+ }
+
+ /**
+ * @return true if the execution path (which includes the base name of the file)
+ * of the given file is matched by the {@link RegexFilter}.
+ */
+ public boolean isIncluded(Artifact artifact) {
+ return regexFilter.isIncluded(artifact.getExecPathString());
+ }
+
+ /**
+ * Returns the list of options to add to a command line.
+ */
+ public List<String> getOptions() {
+ return optionsList;
+ }
+
+ RegexFilter getRegexFilter() {
+ return regexFilter;
+ }
+
+ @Override
+ public String toString() {
+ return regexFilter + " Options: " + optionsList;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ PerLabelOptions otherOptions =
+ other instanceof PerLabelOptions ? (PerLabelOptions) other : null;
+ return this == other || (otherOptions != null &&
+ this.regexFilter.equals(otherOptions.regexFilter) &&
+ this.optionsList.equals(otherOptions.optionsList));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(regexFilter, optionsList);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/RunUnder.java b/src/main/java/com/google/devtools/build/lib/analysis/config/RunUnder.java
new file mode 100644
index 0000000..a51ea25
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/RunUnder.java
@@ -0,0 +1,52 @@
+// 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.config;
+
+import com.google.devtools.build.lib.syntax.Label;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Components of --run_under option.
+ */
+public interface RunUnder extends Serializable {
+ /**
+ * @return the whole value passed to --run_under option.
+ */
+ String getValue();
+
+ /**
+ * Returns label corresponding to the first word (according to shell
+ * tokenization) passed to --run_under.
+ *
+ * @return if the first word (according to shell tokenization) passed to
+ * --run_under starts with {@code "//"} returns the label
+ * corresponding to that word otherwise {@code null}
+ */
+ Label getLabel();
+
+ /**
+ * @return if the first word (according to shell tokenization) passed to
+ * --run_under starts with {@code "//"} returns {@code null}
+ * otherwise the first word
+ */
+ String getCommand();
+
+ /**
+ * @return everything except the first word (according to shell
+ * tokenization) passed to --run_under.
+ */
+ List<String> getOptions();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/RunUnderConverter.java b/src/main/java/com/google/devtools/build/lib/analysis/config/RunUnderConverter.java
new file mode 100644
index 0000000..1f7b660
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/RunUnderConverter.java
@@ -0,0 +1,133 @@
+// 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.config;
+
+import com.google.devtools.build.lib.shell.ShellUtils;
+import com.google.devtools.build.lib.shell.ShellUtils.TokenizationException;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.syntax.Label.SyntaxException;
+import com.google.devtools.common.options.Converter;
+import com.google.devtools.common.options.OptionsParsingException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * --run_under options converter.
+ */
+public class RunUnderConverter implements Converter<RunUnder> {
+ @Override
+ public RunUnder convert(final String input) throws OptionsParsingException {
+ final List<String> runUnderList = new ArrayList<>();
+ try {
+ ShellUtils.tokenize(runUnderList, input);
+ } catch (TokenizationException e) {
+ throw new OptionsParsingException("Not a valid command prefix " + e.getMessage());
+ }
+ if (runUnderList.isEmpty()) {
+ throw new OptionsParsingException("Empty command");
+ }
+ final String runUnderCommand = runUnderList.get(0);
+ if (runUnderCommand.startsWith("//")) {
+ try {
+ final Label runUnderLabel = Label.parseAbsolute(runUnderCommand);
+ return new RunUnderLabel(input, runUnderLabel, runUnderList);
+ } catch (SyntaxException e) {
+ throw new OptionsParsingException("Not a valid label " + e.getMessage());
+ }
+ } else {
+ return new RunUnderCommand(input, runUnderCommand, runUnderList);
+ }
+ }
+
+ private static final class RunUnderLabel implements RunUnder {
+ private final String input;
+ private final Label runUnderLabel;
+ private final List<String> runUnderList;
+
+ public RunUnderLabel(String input, Label runUnderLabel, List<String> runUnderList) {
+ this.input = input;
+ this.runUnderLabel = runUnderLabel;
+ this.runUnderList = new ArrayList<String>(runUnderList.subList(1, runUnderList.size()));
+ }
+
+ @Override public String getValue() { return input; }
+ @Override public Label getLabel() { return runUnderLabel; }
+ @Override public String getCommand() { return null; }
+ @Override public List<String> getOptions() { return runUnderList; }
+ @Override public String toString() { return input; }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (other instanceof RunUnderLabel) {
+ RunUnderLabel otherRunUnderLabel = (RunUnderLabel) other;
+ return Objects.equals(input, otherRunUnderLabel.input)
+ && Objects.equals(runUnderLabel, otherRunUnderLabel.runUnderLabel)
+ && Objects.equals(runUnderList, otherRunUnderLabel.runUnderList);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(input, runUnderLabel, runUnderList);
+ }
+ }
+
+ private static final class RunUnderCommand implements RunUnder {
+ private final String input;
+ private final String runUnderCommand;
+ private final List<String> runUnderList;
+
+ public RunUnderCommand(String input, String runUnderCommand, List<String> runUnderList) {
+ this.input = input;
+ this.runUnderCommand = runUnderCommand;
+ this.runUnderList = new ArrayList<String>(runUnderList.subList(1, runUnderList.size()));
+ }
+
+ @Override public String getValue() { return input; }
+ @Override public Label getLabel() { return null; }
+ @Override public String getCommand() { return runUnderCommand; }
+ @Override public List<String> getOptions() { return runUnderList; }
+ @Override public String toString() { return input; }
+
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (other instanceof RunUnderCommand) {
+ RunUnderCommand otherRunUnderCommand = (RunUnderCommand) other;
+ return Objects.equals(input, otherRunUnderCommand.input)
+ && Objects.equals(runUnderCommand, otherRunUnderCommand.runUnderCommand)
+ && Objects.equals(runUnderList, otherRunUnderCommand.runUnderList);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(input, runUnderCommand, runUnderList);
+ }
+ }
+ @Override
+ public String getTypeDescription() {
+ return "a prefix in front of command";
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/ConstraintSemantics.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/ConstraintSemantics.java
new file mode 100644
index 0000000..14ac2bc
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/ConstraintSemantics.java
@@ -0,0 +1,473 @@
+// Copyright 2015 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.constraints;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.analysis.constraints.EnvironmentCollection.EnvironmentWithGroup;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.EnvironmentGroup;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.syntax.Label;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * Implementation of the semantics of Bazel's constraint specification and enforcement system.
+ *
+ * <p>This is how the system works:
+ *
+ * <p>All build rules can declare which "environments" they can be built for, where an "environment"
+ * is a label instance of an {@link EnvironmentRule} rule declared in a BUILD file. There are
+ * various ways to do this:
+ *
+ * <ul>
+ * <li>Through a "restricted to" attribute setting
+ * ({@link RuleClass#RESTRICTED_ENVIRONMENT_ATTR}). This is the most direct form of
+ * specification - it declares the exact set of environments the rule supports (for its group -
+ * see precise details below).
+ * <li>Through a "compatible with" attribute setting
+ * ({@link RuleClass#COMPATIBLE_ENVIRONMENT_ATTR}. This declares <b>additional</b>
+ * environments a rule supports in addition to "standard" environments that are supported by
+ * default (see below).
+ * <li>Through "default" specifications in {@link EnvironmentGroup} rules. Every environment
+ * belongs to a group of thematically related peers (e.g. "target architectures", "JDK versions",
+ * or "mobile devices"). An environment group's definition includes which of these
+ * environments should be supported "by default" if not otherwise specified by one of the above
+ * mechanisms. In particular, a rule with no environment-related attributes automatically
+ * inherits all defaults.
+ * <li>Through a rule class default ({@link RuleClass.Builder#restrictedTo} and
+ * {@link RuleClass.Builder#compatibleWith}). This overrides global defaults for all instances
+ * of the given rule class. This can be used, for example, to make all *_test rules "testable"
+ * without each instance having to explicitly declare this capability.
+ * </ul>
+ *
+ * <p>Groups exist to model the idea that some environments are related while others have nothing
+ * to do with each other. Say, for example, we want to say a rule works for PowerPC platforms but
+ * not x86. We can do so by setting its "restricted to" attribute to
+ * {@code ['//sample/path:powerpc']}. Because both PowerPC and x86 are in the same
+ * "target architectures" group, this setting removes x86 from the set of supported environments.
+ * But since JDK support belongs to its own group ("JDK versions") it says nothing about which JDK
+ * the rule supports.
+ *
+ * <p>More precisely, if a rule has a "restricted to" value of [A, B, C], this removes support
+ * for all default environments D such that group(D) is in [group(A), group(B), group(C)] AND
+ * D is not in [A, B, C] (in other words, D isn't explicitly opted back in). The rule's full
+ * set of supported environments thus becomes [A, B, C] + all defaults that belong to unrelated
+ * groups.
+ *
+ * <p>If the rule has a "compatible with" value of [E, F, G], these are unconditionally
+ * added to its set of supported environments (in addition to the results from above).
+ *
+ * <p>An environment may not appear in both a rule's "restricted to" and "compatible with" values.
+ * If two environments belong to the same group, they must either both be in "restricted to",
+ * both be in "compatible with", or not explicitly specified.
+ *
+ * <p>Given all the above, constraint enforcement is this: rule A can depend on rule B if, for
+ * every environment A supports, B also supports that environment.
+ */
+public class ConstraintSemantics {
+ private ConstraintSemantics() {
+ }
+
+ /**
+ * Provides a set of default environments for a given environment group.
+ */
+ private interface DefaultsProvider {
+ Collection<Label> getDefaults(EnvironmentGroup group);
+ }
+
+ /**
+ * Provides a group's defaults as specified in the environment group's BUILD declaration.
+ */
+ private static class GroupDefaultsProvider implements DefaultsProvider {
+ @Override
+ public Collection<Label> getDefaults(EnvironmentGroup group) {
+ return group.getDefaults();
+ }
+ }
+
+ /**
+ * Provides a group's defaults, factoring in rule class defaults as specified by
+ * {@link com.google.devtools.build.lib.packages.RuleClass.Builder#compatibleWith}
+ * and {@link com.google.devtools.build.lib.packages.RuleClass.Builder#restrictedTo}.
+ */
+ private static class RuleClassDefaultsProvider implements DefaultsProvider {
+ private final EnvironmentCollection ruleClassDefaults;
+ private final GroupDefaultsProvider groupDefaults;
+
+ RuleClassDefaultsProvider(EnvironmentCollection ruleClassDefaults) {
+ this.ruleClassDefaults = ruleClassDefaults;
+ this.groupDefaults = new GroupDefaultsProvider();
+ }
+
+ @Override
+ public Collection<Label> getDefaults(EnvironmentGroup group) {
+ if (ruleClassDefaults.getGroups().contains(group)) {
+ return ruleClassDefaults.getEnvironments(group);
+ } else {
+ // If there are no rule class defaults for this group, just inherit global defaults.
+ return groupDefaults.getDefaults(group);
+ }
+ }
+ }
+
+ /**
+ * Collects the set of supported environments for a given rule by merging its
+ * restriction-style and compatibility-style environment declarations as specified by
+ * the given attributes. Only includes environments from "known" groups, i.e. the groups
+ * owning the environments explicitly referenced from these attributes.
+ */
+ private static class EnvironmentCollector {
+ private final RuleContext ruleContext;
+ private final String restrictionAttr;
+ private final String compatibilityAttr;
+ private final DefaultsProvider defaultsProvider;
+
+ private final EnvironmentCollection restrictionEnvironments;
+ private final EnvironmentCollection compatibilityEnvironments;
+ private final EnvironmentCollection supportedEnvironments;
+
+ /**
+ * Constructs a new collector on the given attributes.
+ *
+ * @param ruleContext analysis context for the rule
+ * @param restrictionAttr the name of the attribute that declares "restricted to"-style
+ * environments. If the rule doesn't have this attribute, this is considered an
+ * empty declaration.
+ * @param compatibilityAttr the name of the attribute that declares "compatible with"-style
+ * environments. If the rule doesn't have this attribute, this is considered an
+ * empty declaration.
+ * @param defaultsProvider provider for the default environments within a group if not
+ * otherwise overriden by the above attributes
+ */
+ EnvironmentCollector(RuleContext ruleContext, String restrictionAttr, String compatibilityAttr,
+ DefaultsProvider defaultsProvider) {
+ this.ruleContext = ruleContext;
+ this.restrictionAttr = restrictionAttr;
+ this.compatibilityAttr = compatibilityAttr;
+ this.defaultsProvider = defaultsProvider;
+
+ EnvironmentCollection.Builder environmentsBuilder = new EnvironmentCollection.Builder();
+ restrictionEnvironments = collectRestrictionEnvironments(environmentsBuilder);
+ compatibilityEnvironments = collectCompatibilityEnvironments(environmentsBuilder);
+ supportedEnvironments = environmentsBuilder.build();
+ }
+
+ /**
+ * Returns the set of environments supported by this rule, as determined by the
+ * restriction-style attribute, compatibility-style attribute, and group defaults
+ * provider instantiated with this class.
+ */
+ EnvironmentCollection getEnvironments() {
+ return supportedEnvironments;
+ }
+
+ /**
+ * Validity-checks that no group has its environment referenced in both the "compatible with"
+ * and restricted to" attributes. Returns true if all is good, returns false and reports
+ * appropriate errors if there are any problems.
+ */
+ boolean validateEnvironmentSpecifications() {
+ ImmutableCollection<EnvironmentGroup> restrictionGroups = restrictionEnvironments.getGroups();
+ boolean hasErrors = false;
+
+ for (EnvironmentGroup group : compatibilityEnvironments.getGroups()) {
+ if (restrictionGroups.contains(group)) {
+ // To avoid error-spamming the user, when we find a conflict we only report one example
+ // environment from each attribute for that group.
+ Label compatibilityEnv =
+ compatibilityEnvironments.getEnvironments(group).iterator().next();
+ Label restrictionEnv = restrictionEnvironments.getEnvironments(group).iterator().next();
+
+ if (compatibilityEnv.equals(restrictionEnv)) {
+ ruleContext.attributeError(compatibilityAttr, compatibilityEnv
+ + " cannot appear both here and in " + restrictionAttr);
+ } else {
+ ruleContext.attributeError(compatibilityAttr, compatibilityEnv + " and "
+ + restrictionEnv + " belong to the same environment group. They should be declared "
+ + "together either here or in " + restrictionAttr);
+ }
+ hasErrors = true;
+ }
+ }
+
+ return !hasErrors;
+ }
+
+ /**
+ * Adds environments specified in the "restricted to" attribute to the set of supported
+ * environments and returns the environments added.
+ */
+ private EnvironmentCollection collectRestrictionEnvironments(
+ EnvironmentCollection.Builder supportedEnvironments) {
+ return collectEnvironments(restrictionAttr, supportedEnvironments);
+ }
+
+ /**
+ * Adds environments specified in the "compatible with" attribute to the set of supported
+ * environments, along with all defaults from the groups they belong to. Returns these
+ * environments, not including the defaults.
+ */
+ private EnvironmentCollection collectCompatibilityEnvironments(
+ EnvironmentCollection.Builder supportedEnvironments) {
+ EnvironmentCollection compatibilityEnvironments =
+ collectEnvironments(compatibilityAttr, supportedEnvironments);
+ for (EnvironmentGroup group : compatibilityEnvironments.getGroups()) {
+ supportedEnvironments.putAll(group, defaultsProvider.getDefaults(group));
+ }
+ return compatibilityEnvironments;
+ }
+
+ /**
+ * Adds environments specified by the given attribute to the set of supported environments
+ * and returns the environments added.
+ *
+ * <p>If this rule doesn't have the given attributes, returns an empty set.
+ */
+ private EnvironmentCollection collectEnvironments(String attrName,
+ EnvironmentCollection.Builder supportedEnvironments) {
+ if (!ruleContext.getRule().isAttrDefined(attrName, Type.LABEL_LIST)) {
+ return EnvironmentCollection.EMPTY;
+ }
+ EnvironmentCollection.Builder environments = new EnvironmentCollection.Builder();
+ for (TransitiveInfoCollection envTarget :
+ ruleContext.getPrerequisites(attrName, RuleConfiguredTarget.Mode.DONT_CHECK)) {
+ EnvironmentWithGroup envInfo = resolveEnvironment(envTarget);
+ environments.put(envInfo.group(), envInfo.environment());
+ supportedEnvironments.put(envInfo.group(), envInfo.environment());
+ }
+ return environments.build();
+ }
+
+ /**
+ * Returns the environment and its group. An {@link Environment} rule only "supports" one
+ * environment: itself. Extract that from its more generic provider interface and sanity
+ * check that that's in fact what we see.
+ */
+ private static EnvironmentWithGroup resolveEnvironment(TransitiveInfoCollection envRule) {
+ SupportedEnvironmentsProvider prereq =
+ Preconditions.checkNotNull(envRule.getProvider(SupportedEnvironmentsProvider.class));
+ return Iterables.getOnlyElement(prereq.getEnvironments().getGroupedEnvironments());
+ }
+ }
+
+ /**
+ * Returns the set of environments this rule supports, applying the logic described in
+ * {@link ConstraintSemantics}.
+ *
+ * <p>Note this set is <b>not complete</b> - it doesn't include environments from groups we don't
+ * "know about". Environments and groups can be declared in any package. If the rule includes
+ * no references to that package, then it simply doesn't know anything about them. But the
+ * constraint semantics say the rule should support the defaults for that group. We encode this
+ * implicitly: given the returned set, for any group that's not in the set the rule is also
+ * considered to support that group's defaults.
+ *
+ * @param ruleContext analysis context for the rule. A rule error is triggered here if
+ * invalid constraint settings are discovered.
+ * @return the environments this rule supports, not counting defaults "unknown" to this rule
+ * as described above. Returns null if any errors are encountered.
+ */
+ @Nullable
+ public static EnvironmentCollection getSupportedEnvironments(RuleContext ruleContext) {
+ if (!validateAttributes(ruleContext)) {
+ return null;
+ }
+
+ // This rule's rule class defaults (or null if the rule class has no defaults).
+ EnvironmentCollector ruleClassCollector = maybeGetRuleClassDefaults(ruleContext);
+ // Default environments for this rule. If the rule has rule class defaults, this is
+ // those defaults. Otherwise it's the global defaults specified by environment_group
+ // declarations.
+ DefaultsProvider ruleDefaults;
+
+ if (ruleClassCollector != null) {
+ if (!ruleClassCollector.validateEnvironmentSpecifications()) {
+ return null;
+ }
+ ruleDefaults = new RuleClassDefaultsProvider(ruleClassCollector.getEnvironments());
+ } else {
+ ruleDefaults = new GroupDefaultsProvider();
+ }
+
+ EnvironmentCollector ruleCollector = new EnvironmentCollector(ruleContext,
+ RuleClass.RESTRICTED_ENVIRONMENT_ATTR, RuleClass.COMPATIBLE_ENVIRONMENT_ATTR, ruleDefaults);
+ if (!ruleCollector.validateEnvironmentSpecifications()) {
+ return null;
+ }
+
+ EnvironmentCollection supportedEnvironments = ruleCollector.getEnvironments();
+ if (ruleClassCollector != null) {
+ // If we have rule class defaults from groups that aren't referenced from the rule itself,
+ // we need to add them in too to override the global defaults.
+ supportedEnvironments =
+ addUnknownGroupsToCollection(supportedEnvironments, ruleClassCollector.getEnvironments());
+ }
+ return supportedEnvironments;
+ }
+
+ /**
+ * Returns the rule class defaults specified for this rule, or null if there are
+ * no such defaults.
+ */
+ @Nullable
+ private static EnvironmentCollector maybeGetRuleClassDefaults(RuleContext ruleContext) {
+ Rule rule = ruleContext.getRule();
+ String restrictionAttr = RuleClass.DEFAULT_RESTRICTED_ENVIRONMENT_ATTR;
+ String compatibilityAttr = RuleClass.DEFAULT_COMPATIBLE_ENVIRONMENT_ATTR;
+
+ if (rule.isAttrDefined(restrictionAttr, Type.LABEL_LIST)
+ || rule.isAttrDefined(compatibilityAttr, Type.LABEL_LIST)) {
+ return new EnvironmentCollector(ruleContext, restrictionAttr, compatibilityAttr,
+ new GroupDefaultsProvider());
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Adds environments to an {@link EnvironmentCollection} from groups that aren't already
+ * a part of that collection.
+ *
+ * @param environments the collection to add to
+ * @param toAdd the collection to add. All environments in this collection in groups
+ * that aren't represented in {@code environments} are added to {@code environments}.
+ * @return the expanded collection.
+ */
+ private static EnvironmentCollection addUnknownGroupsToCollection(
+ EnvironmentCollection environments, EnvironmentCollection toAdd) {
+ EnvironmentCollection.Builder builder = new EnvironmentCollection.Builder();
+ builder.putAll(environments);
+ for (EnvironmentGroup candidateGroup : toAdd.getGroups()) {
+ if (!environments.getGroups().contains(candidateGroup)) {
+ builder.putAll(candidateGroup, toAdd.getEnvironments(candidateGroup));
+ }
+ }
+ return builder.build();
+ }
+
+ /**
+ * Validity-checks this rule's constraint-related attributes. Returns true if all is good,
+ * returns false and reports appropriate errors if there are any problems.
+ */
+ private static boolean validateAttributes(RuleContext ruleContext) {
+ AttributeMap attributes = ruleContext.attributes();
+
+ // Report an error if "restricted to" is explicitly set to nothing. Even if this made
+ // conceptual sense, we don't know which groups we should apply that to.
+ String restrictionAttr = RuleClass.RESTRICTED_ENVIRONMENT_ATTR;
+ List<? extends TransitiveInfoCollection> restrictionEnvironments = ruleContext
+ .getPrerequisites(restrictionAttr, RuleConfiguredTarget.Mode.DONT_CHECK);
+ if (restrictionEnvironments.isEmpty()
+ && attributes.isAttributeValueExplicitlySpecified(restrictionAttr)) {
+ ruleContext.attributeError(restrictionAttr, "attribute cannot be empty");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Performs constraint checking on the given rule's dependencies and reports any errors.
+ *
+ * @param ruleContext the rule to analyze
+ * @param supportedEnvironments the rule's supported environments, as defined by the return
+ * value of {@link #getSupportedEnvironments}. In particular, for any environment group that's
+ * not in this collection, the rule is assumed to support the defaults for that group.
+ */
+ public static void checkConstraints(RuleContext ruleContext,
+ EnvironmentCollection supportedEnvironments) {
+
+ Set<EnvironmentGroup> knownGroups = supportedEnvironments.getGroups();
+
+ for (TransitiveInfoCollection dependency : getAllPrerequisites(ruleContext)) {
+ SupportedEnvironmentsProvider depProvider =
+ dependency.getProvider(SupportedEnvironmentsProvider.class);
+ if (depProvider == null) {
+ // Input files (InputFileConfiguredTarget) don't support environments. We may subsequently
+ // opt them into constraint checking, but for now just pass them by.
+ continue;
+ }
+ Collection<Label> depEnvironments = depProvider.getEnvironments().getEnvironments();
+ Set<EnvironmentGroup> groupsKnownToDep = depProvider.getEnvironments().getGroups();
+
+ // Environments we support that the dependency does not support.
+ Set<Label> disallowedEnvironments = new LinkedHashSet<>();
+
+ // For every environment we support, either the dependency must also support it OR it must be
+ // a default for a group the dependency doesn't know about.
+ for (EnvironmentWithGroup supportedEnv : supportedEnvironments.getGroupedEnvironments()) {
+ EnvironmentGroup group = supportedEnv.group();
+ Label environment = supportedEnv.environment();
+ if (!depEnvironments.contains(environment)
+ && (groupsKnownToDep.contains(group) || !group.isDefault(environment))) {
+ disallowedEnvironments.add(environment);
+ }
+ }
+
+ // For any environment group we don't know about, we implicitly support its defaults. Check
+ // that the dep does, too.
+ for (EnvironmentGroup depGroup : groupsKnownToDep) {
+ if (!knownGroups.contains(depGroup)) {
+ for (Label defaultEnv : depGroup.getDefaults()) {
+ if (!depEnvironments.contains(defaultEnv)) {
+ disallowedEnvironments.add(defaultEnv);
+ }
+ }
+ }
+ }
+
+ // Report errors on bad environments.
+ if (!disallowedEnvironments.isEmpty()) {
+ ruleContext.ruleError("dependency " + dependency.getLabel()
+ + " doesn't support expected environment"
+ + (disallowedEnvironments.size() == 1 ? "" : "s")
+ + ": " + Joiner.on(", ").join(disallowedEnvironments));
+ }
+ }
+ }
+
+ /**
+ * Returns all dependencies that should be constraint-checked against the current rule.
+ */
+ private static Iterable<TransitiveInfoCollection> getAllPrerequisites(RuleContext ruleContext) {
+ Set<TransitiveInfoCollection> prerequisites = new LinkedHashSet<>();
+ AttributeMap attributes = ruleContext.attributes();
+
+ for (String attr : attributes.getAttributeNames()) {
+ Type<?> attrType = attributes.getAttributeType(attr);
+ // TODO(bazel-team): support specifying which attributes are subject to constraint checking
+ if ((attrType == Type.LABEL || attrType == Type.LABEL_LIST)
+ && !RuleClass.isConstraintAttribute(attr)
+ && !attr.equals("visibility")) {
+ prerequisites.addAll(
+ ruleContext.getPrerequisites(attr, RuleConfiguredTarget.Mode.DONT_CHECK));
+ }
+ }
+ return prerequisites;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/Environment.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/Environment.java
new file mode 100644
index 0000000..912ed72
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/Environment.java
@@ -0,0 +1,71 @@
+// Copyright 2015 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.constraints;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.FileProvider;
+import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.RunfilesProvider;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.packages.EnvironmentGroup;
+import com.google.devtools.build.lib.packages.Package;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+import com.google.devtools.build.lib.syntax.Label;
+
+/**
+ * Implementation for the environment rule.
+ */
+public class Environment implements RuleConfiguredTargetFactory {
+
+ @Override
+ public ConfiguredTarget create(RuleContext ruleContext) {
+
+ // The main analysis work to do here is to simply fill in SupportedEnvironmentsProvider to
+ // pass the environment itself to depending rules.
+ //
+ // This will likely expand when we add support for environments fulfilling other environments.
+ Label label = ruleContext.getLabel();
+ Package pkg = ruleContext.getRule().getPackage();
+
+ EnvironmentGroup group = null;
+ for (EnvironmentGroup pkgGroup : pkg.getTargets(EnvironmentGroup.class)) {
+ if (pkgGroup.getEnvironments().contains(label)) {
+ group = pkgGroup;
+ break;
+ }
+ }
+
+ if (group == null) {
+ ruleContext.ruleError("no matching environment group from the same package");
+ return null;
+ }
+
+ return new RuleConfiguredTargetBuilder(ruleContext)
+ .addProvider(SupportedEnvironmentsProvider.class,
+ new SupportedEnvironments(
+ new EnvironmentCollection.Builder().put(group, label).build()))
+ .addProvider(RunfilesProvider.class, RunfilesProvider.EMPTY)
+ .add(FileProvider.class, new FileProvider(ruleContext.getLabel(),
+ NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER)))
+ .add(FilesToRunProvider.class, new FilesToRunProvider(ruleContext.getLabel(),
+ ImmutableList.<Artifact>of(), null, null))
+ .build();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/EnvironmentCollection.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/EnvironmentCollection.java
new file mode 100644
index 0000000..1ce5f1c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/EnvironmentCollection.java
@@ -0,0 +1,126 @@
+// Copyright 2015 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.constraints;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.packages.EnvironmentGroup;
+import com.google.devtools.build.lib.syntax.Label;
+
+import java.util.Map;
+
+/**
+ * Contains a set of {@link Environment} labels and their associated groups.
+ */
+@Immutable
+public class EnvironmentCollection {
+ private final ImmutableMultimap<EnvironmentGroup, Label> map;
+
+ private EnvironmentCollection(ImmutableMultimap<EnvironmentGroup, Label> map) {
+ this.map = map;
+ }
+
+ /**
+ * Stores an environment's build label along with the group it belongs to.
+ */
+ static class EnvironmentWithGroup {
+ private final Label environment;
+ private final EnvironmentGroup group;
+ EnvironmentWithGroup(Label environment, EnvironmentGroup group) {
+ this.environment = environment;
+ this.group = group;
+ }
+ Label environment() { return environment; }
+ EnvironmentGroup group() { return group; }
+ }
+
+ /**
+ * Returns the build labels of each environment in this collection, ordered by
+ * their insertion order in {@link Builder}.
+ */
+ ImmutableCollection<Label> getEnvironments() {
+ return map.values();
+ }
+
+ /**
+ * Returns the set of groups the environments in this collection belong to, ordered by
+ * their insertion order in {@link Builder}
+ */
+ ImmutableSet<EnvironmentGroup> getGroups() {
+ return map.keySet();
+ }
+
+ /**
+ * Returns the build labels of each environment in this collection paired with the
+ * group each environment belongs to, ordered by their insertion order in {@link Builder}.
+ */
+ ImmutableCollection<EnvironmentWithGroup> getGroupedEnvironments() {
+ ImmutableSet.Builder<EnvironmentWithGroup> builder = ImmutableSet.builder();
+ for (Map.Entry<EnvironmentGroup, Label> entry : map.entries()) {
+ builder.add(new EnvironmentWithGroup(entry.getValue(), entry.getKey()));
+ }
+ return builder.build();
+ }
+
+ /**
+ * Returns the environments in this collection that belong to the given group, ordered by
+ * their insertion order in {@link Builder}. If no environments belong to the given group,
+ * returns an empty collection.
+ */
+ ImmutableCollection<Label> getEnvironments(EnvironmentGroup group) {
+ return map.get(group);
+ }
+
+ /**
+ * An empty collection.
+ */
+ static final EnvironmentCollection EMPTY =
+ new EnvironmentCollection(ImmutableMultimap.<EnvironmentGroup, Label>of());
+
+ static class Builder {
+ private final ImmutableMultimap.Builder<EnvironmentGroup, Label> mapBuilder =
+ ImmutableMultimap.builder();
+
+ /**
+ * Inserts the given environment / owning group pair.
+ */
+ Builder put(EnvironmentGroup group, Label environment) {
+ mapBuilder.put(group, environment);
+ return this;
+ }
+
+ /**
+ * Inserts the given set of environments, all belonging to the specified group.
+ */
+ Builder putAll(EnvironmentGroup group, Iterable<Label> environments) {
+ mapBuilder.putAll(group, environments);
+ return this;
+ }
+
+ /**
+ * Inserts the contents of another {@link EnvironmentCollection} into this one.
+ */
+ Builder putAll(EnvironmentCollection other) {
+ mapBuilder.putAll(other.map);
+ return this;
+ }
+
+ EnvironmentCollection build() {
+ return new EnvironmentCollection(mapBuilder.build());
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/EnvironmentRule.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/EnvironmentRule.java
new file mode 100644
index 0000000..0553af0
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/EnvironmentRule.java
@@ -0,0 +1,48 @@
+// Copyright 2015 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.constraints;
+
+import static com.google.devtools.build.lib.packages.Attribute.attr;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.analysis.BaseRuleClasses;
+import com.google.devtools.build.lib.analysis.BlazeRule;
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.Type;
+
+/**
+ * Rule definition for environment rules (for Bazel's constraint enforcement system).
+ */
+@BlazeRule(name = EnvironmentRule.RULE_NAME,
+ ancestors = { BaseRuleClasses.BaseRule.class },
+ factoryClass = Environment.class)
+public final class EnvironmentRule implements RuleDefinition {
+ public static final String RULE_NAME = "environment";
+
+ @Override
+ public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) {
+ return builder
+ .override(attr("tags", Type.STRING_LIST)
+ // No need to show up in ":all", etc. target patterns.
+ .value(ImmutableList.of("manual"))
+ .nonconfigurable("low-level attribute, used in TargetUtils without configurations"))
+ .removeAttribute(RuleClass.COMPATIBLE_ENVIRONMENT_ATTR)
+ .removeAttribute(RuleClass.RESTRICTED_ENVIRONMENT_ATTR)
+ .setUndocumented()
+ .build();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironments.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironments.java
new file mode 100644
index 0000000..78c835e
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironments.java
@@ -0,0 +1,31 @@
+// Copyright 2015 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.constraints;
+
+/**
+ * Standard {@link SupportedEnvironmentsProvider} implementation.
+ */
+public class SupportedEnvironments implements SupportedEnvironmentsProvider {
+ private final EnvironmentCollection supportedEnvironments;
+
+ public SupportedEnvironments(EnvironmentCollection supportedEnvironments) {
+ this.supportedEnvironments = supportedEnvironments;
+ }
+
+ @Override
+ public EnvironmentCollection getEnvironments() {
+ return supportedEnvironments;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironmentsProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironmentsProvider.java
new file mode 100644
index 0000000..8200386
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/constraints/SupportedEnvironmentsProvider.java
@@ -0,0 +1,29 @@
+// Copyright 2015 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.constraints;
+
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+
+/**
+ * A provider that advertises which environments the associated target is compatible with
+ * (from the point of view of the constraint enforcement system).
+ */
+public interface SupportedEnvironmentsProvider extends TransitiveInfoProvider {
+
+ /**
+ * Returns the environments the associated target is compatible with.
+ */
+ EnvironmentCollection getEnvironments();
+}