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/&lt;fragment&gt;/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/&lt;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 &lt;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();
+}