A prototype implementation of top-level aspects.

--
MOS_MIGRATED_REVID=101033236
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
index 3f4a06e..95f0683 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/Aspect.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/Aspect.java
@@ -17,10 +17,16 @@
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
 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.concurrent.ThreadSafety.Immutable;
 
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.TreeMap;
+
+import javax.annotation.Nullable;
 
 /**
  * Extra information about a configured target computed on request of a dependent.
@@ -34,15 +40,25 @@
  */
 @Immutable
 public final class Aspect implements Iterable<TransitiveInfoProvider> {
-  private final
-      ImmutableMap<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> providers;
+  private final String name;
+  private final ImmutableMap<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider>
+      providers;
 
   private Aspect(
+      String name,
       ImmutableMap<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> providers) {
+    this.name = name;
     this.providers = providers;
   }
 
   /**
+   * Returns the aspect name.
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
    * Returns the providers created by the aspect.
    */
   public ImmutableMap<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider>
@@ -50,6 +66,14 @@
     return providers;
   }
 
+
+  @Nullable
+  <P extends TransitiveInfoProvider> P getProvider(Class<P> providerClass) {
+    AnalysisUtils.checkProvider(providerClass);
+
+    return providerClass.cast(providers.get(providerClass));
+  }
+
   @Override
   public UnmodifiableIterator<TransitiveInfoProvider> iterator() {
     return providers.values().iterator();
@@ -61,6 +85,12 @@
   public static class Builder {
     private final Map<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider>
         providers = new LinkedHashMap<>();
+    private final Map<String, NestedSetBuilder<Artifact>> outputGroupBuilders = new TreeMap<>();
+    private final String name;
+
+    public Builder(String name) {
+      this.name = name;
+    }
 
     /**
      * Adds a provider to the aspect.
@@ -75,8 +105,35 @@
       return this;
     }
 
+    /**
+     * Adds a set of files to an output group.
+     */
+    public Builder addOutputGroup(String name, NestedSet<Artifact> artifacts) {
+      NestedSetBuilder<Artifact> nestedSetBuilder = outputGroupBuilders.get(name);
+      if (nestedSetBuilder == null) {
+        nestedSetBuilder = NestedSetBuilder.<Artifact>stableOrder();
+        outputGroupBuilders.put(name, nestedSetBuilder);
+      }
+      nestedSetBuilder.addTransitive(artifacts);
+      return this;
+    }
+
+
     public Aspect build() {
-      return new Aspect(ImmutableMap.copyOf(providers));
+      if (!outputGroupBuilders.isEmpty()) {
+        ImmutableMap.Builder<String, NestedSet<Artifact>> outputGroups = ImmutableMap.builder();
+        for (Map.Entry<String, NestedSetBuilder<Artifact>> entry : outputGroupBuilders.entrySet()) {
+          outputGroups.put(entry.getKey(), entry.getValue().build());
+        }
+
+        if (providers.containsKey(OutputGroupProvider.class)) {
+          throw new IllegalStateException(
+              "OutputGroupProvider was provided explicitly; do not use addOutputGroup");
+        }
+        addProvider(OutputGroupProvider.class, new OutputGroupProvider(outputGroups.build()));
+      }
+
+      return new Aspect(name, ImmutableMap.copyOf(providers));
     }
   }
 }
\ No newline at end of file
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AspectCompleteEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/AspectCompleteEvent.java
new file mode 100644
index 0000000..faefb3c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AspectCompleteEvent.java
@@ -0,0 +1,73 @@
+// 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;
+
+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.skyframe.AspectValue;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.skyframe.SkyValue;
+
+/**
+ * This event is fired as soon as a top-level aspect is either built or fails.
+ */
+public class AspectCompleteEvent implements SkyValue {
+  private final AspectValue aspectValue;
+  private final NestedSet<Label> rootCauses;
+
+  private AspectCompleteEvent(AspectValue aspectValue, NestedSet<Label> rootCauses) {
+    this.aspectValue = aspectValue;
+    this.rootCauses =
+        (rootCauses == null) ? NestedSetBuilder.<Label>emptySet(Order.STABLE_ORDER) : rootCauses;
+  }
+
+  /**
+   * Construct a successful target completion event.
+   */
+  public static AspectCompleteEvent createSuccessful(AspectValue value) {
+    return new AspectCompleteEvent(value, null);
+  }
+
+  /**
+   * Construct a target completion event for a failed target, with the given non-empty root causes.
+   */
+  public static AspectCompleteEvent createFailed(AspectValue value, NestedSet<Label> rootCauses) {
+    Preconditions.checkArgument(!Iterables.isEmpty(rootCauses));
+    return new AspectCompleteEvent(value, rootCauses);
+  }
+
+  /**
+   * Returns the target associated with the event.
+   */
+  public AspectValue getAspectValue() {
+    return aspectValue;
+  }
+
+  /**
+   * 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/BuildView.java b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
index 3baae74..7ce8c04 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
@@ -65,13 +65,15 @@
 import com.google.devtools.build.lib.rules.test.CoverageReportActionFactory;
 import com.google.devtools.build.lib.rules.test.CoverageReportActionFactory.CoverageReportActionsWrapper;
 import com.google.devtools.build.lib.skyframe.ActionLookupValue;
+import com.google.devtools.build.lib.skyframe.AspectValue;
+import com.google.devtools.build.lib.skyframe.AspectValue.AspectKey;
 import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
 import com.google.devtools.build.lib.skyframe.CoverageReportValue;
+import com.google.devtools.build.lib.skyframe.SkyframeAnalysisResult;
 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.Pair;
 import com.google.devtools.build.lib.util.RegexFilter;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.skyframe.SkyKey;
@@ -79,6 +81,7 @@
 import com.google.devtools.common.options.Option;
 import com.google.devtools.common.options.OptionsBase;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -434,12 +437,17 @@
    */
   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);
+    public static final AnalysisResult EMPTY =
+        new AnalysisResult(
+            ImmutableList.<ConfiguredTarget>of(),
+            ImmutableList.<AspectValue>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;
@@ -449,13 +457,20 @@
     private final ImmutableSet<ConfiguredTarget> parallelTests;
     private final ImmutableSet<ConfiguredTarget> exclusiveTests;
     @Nullable private final TopLevelArtifactContext topLevelContext;
+    private final ImmutableList<AspectValue> aspects;
 
     private AnalysisResult(
-        Collection<ConfiguredTarget> targetsToBuild, Collection<ConfiguredTarget> targetsToTest,
-        @Nullable String error, ActionGraph actionGraph,
-        Collection<Artifact> artifactsToBuild, Collection<ConfiguredTarget> parallelTests,
-        Collection<ConfiguredTarget> exclusiveTests, TopLevelArtifactContext topLevelContext) {
+        Collection<ConfiguredTarget> targetsToBuild,
+        Collection<AspectValue> aspects,
+        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.aspects = ImmutableList.copyOf(aspects);
       this.targetsToTest = targetsToTest == null ? null : ImmutableList.copyOf(targetsToTest);
       this.error = error;
       this.actionGraph = actionGraph;
@@ -473,6 +488,16 @@
     }
 
     /**
+     * Returns aspects of configured targets to build.
+     *
+     * <p>If this list is empty, build the targets returned by {@code getTargetsToBuild()}.
+     * Otherwise, only build these aspects of the targets returned by {@code getTargetsToBuild()}.
+     */
+    public Collection<AspectValue> getAspects() {
+      return aspects;
+    }
+
+    /**
      * Returns the configured targets to run as tests, or {@code null} if testing was not
      * requested (e.g. "build" command rather than "test" command).
      */
@@ -520,10 +545,11 @@
   static Iterable<? extends ConfiguredTarget> filterTestsByTargets(
       Collection<? extends ConfiguredTarget> targets,
       final Set<? extends Target> allowedTargets) {
-    return Iterables.filter(targets,
+    return Iterables.filter(
+        targets,
         new Predicate<ConfiguredTarget>() {
           @Override
-              public boolean apply(ConfiguredTarget rule) {
+          public boolean apply(ConfiguredTarget rule) {
             return allowedTargets.contains(rule.getTarget());
           }
         });
@@ -536,10 +562,15 @@
   }
 
   @ThreadCompatible
-  public AnalysisResult update(LoadingResult loadingResult,
-      BuildConfigurationCollection configurations, Options viewOptions,
-      TopLevelArtifactContext topLevelOptions, EventHandler eventHandler, EventBus eventBus)
-          throws ViewCreationFailedException, InterruptedException {
+  public AnalysisResult update(
+      LoadingResult loadingResult,
+      BuildConfigurationCollection configurations,
+      List<String> aspects,
+      Options viewOptions,
+      TopLevelArtifactContext topLevelOptions,
+      EventHandler eventHandler,
+      EventBus eventBus)
+      throws ViewCreationFailedException, InterruptedException {
     LOG.info("Starting analysis");
     pollInterruptedStatus();
 
@@ -580,21 +611,36 @@
           }
         });
 
+    List<AspectKey> aspectKeys = new ArrayList<>();
+    for (String aspect : aspects) {
+      @SuppressWarnings("unchecked")
+      final Class<? extends ConfiguredAspectFactory> aspectFactoryClass =
+          (Class<? extends ConfiguredAspectFactory>)
+              ruleClassProvider.getAspectFactoryMap().get(aspect);
+      if (aspectFactoryClass != null) {
+        for (ConfiguredTargetKey targetSpec : targetSpecs) {
+          aspectKeys.add(
+              AspectValue.createAspectKey(
+                  targetSpec.getLabel(), targetSpec.getConfiguration(), aspectFactoryClass));
+        }
+      } else {
+        throw new ViewCreationFailedException("Aspect '" + aspect + "' is unknown");
+      }
+    }
+
     prepareToBuild(new SkyframePackageRootResolver(skyframeExecutor));
     skyframeExecutor.injectWorkspaceStatusData();
-    Collection<ConfiguredTarget> configuredTargets;
-    WalkableGraph graph;
+    SkyframeAnalysisResult skyframeAnalysisResult;
     try {
-      Pair<Collection<ConfiguredTarget>, WalkableGraph> configuredTargetsResult =
-          skyframeBuildView.configureTargets(targetSpecs, eventBus, viewOptions.keepGoing);
-      configuredTargets = configuredTargetsResult.getFirst();
-      graph = configuredTargetsResult.getSecond();
+      skyframeAnalysisResult =
+          skyframeBuildView.configureTargets(
+              targetSpecs, aspectKeys, eventBus, viewOptions.keepGoing);
     } finally {
       skyframeBuildView.clearInvalidatedConfiguredTargets();
     }
 
     int numTargetsToAnalyze = nodes.size();
-    int numSuccessful = configuredTargets.size();
+    int numSuccessful = skyframeAnalysisResult.getConfiguredTargets().size();
     boolean analysisSuccessful = (numSuccessful == numTargetsToAnalyze);
     if (0 < numSuccessful && numSuccessful < numTargetsToAnalyze) {
       String msg = String.format("Analysis succeeded for only %d of %d top-level targets",
@@ -603,17 +649,28 @@
       LOG.info(msg);
     }
 
-    AnalysisResult result = createResult(loadingResult, topLevelOptions,
-        viewOptions, configuredTargets, analysisSuccessful, graph);
+    AnalysisResult result =
+        createResult(
+            loadingResult,
+            topLevelOptions,
+            viewOptions,
+            skyframeAnalysisResult.getConfiguredTargets(),
+            skyframeAnalysisResult.getAspects(),
+            skyframeAnalysisResult.getWalkableGraph(),
+            analysisSuccessful);
     LOG.info("Finished analysis");
     return result;
   }
 
-  private AnalysisResult createResult(LoadingResult loadingResult,
-      TopLevelArtifactContext topLevelOptions, BuildView.Options viewOptions,
-      Collection<ConfiguredTarget> configuredTargets, boolean analysisSuccessful,
-      final WalkableGraph graph)
-          throws InterruptedException {
+  private AnalysisResult createResult(
+      LoadingResult loadingResult,
+      TopLevelArtifactContext topLevelOptions,
+      BuildView.Options viewOptions,
+      Collection<ConfiguredTarget> configuredTargets,
+      Collection<AspectValue> aspects,
+      final WalkableGraph graph,
+      boolean analysisSuccessful)
+      throws InterruptedException {
     Collection<Target> testsToRun = loadingResult.getTestsToRun();
     Collection<ConfiguredTarget> allTargetsToTest = null;
     if (testsToRun != null) {
@@ -667,8 +724,16 @@
         return null;
       }
     };
-    return new AnalysisResult(configuredTargets, allTargetsToTest, error, actionGraph,
-        artifactsToBuild, parallelTests, exclusiveTests, topLevelOptions);
+    return new AnalysisResult(
+        configuredTargets,
+        aspects,
+        allTargetsToTest,
+        error,
+        actionGraph,
+        artifactsToBuild,
+        parallelTests,
+        exclusiveTests,
+        topLevelOptions);
   }
 
   private static NestedSet<Artifact> getBaselineCoverageArtifacts(
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
index 3b40300..543c71e 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java
@@ -31,6 +31,7 @@
 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.AspectFactory;
 import com.google.devtools.build.lib.packages.Attribute;
 import com.google.devtools.build.lib.packages.RuleClass;
 import com.google.devtools.build.lib.packages.RuleClassProvider;
@@ -83,6 +84,8 @@
     private final Map<String, RuleClass> ruleClassMap = new HashMap<>();
     private final  Map<String, Class<? extends RuleDefinition>> ruleDefinitionMap =
         new HashMap<>();
+    private final Map<String, Class<? extends AspectFactory<?, ?, ?>>> aspectFactoryMap =
+        new HashMap<>();
     private final Map<Class<? extends RuleDefinition>, RuleClass> ruleMap = new HashMap<>();
     private final Map<Class<? extends RuleDefinition>, RuleDefinition> ruleDefinitionInstanceCache =
         new HashMap<>();
@@ -117,6 +120,13 @@
       return this;
     }
 
+    public Builder addAspectFactory(
+        String name, Class<? extends AspectFactory<?, ?, ?>> configuredAspectFactoryClass) {
+      aspectFactoryMap.put(name, configuredAspectFactoryClass);
+
+      return this;
+    }
+
     public Builder addConfigurationOptions(Class<? extends FragmentOptions> configurationOptions) {
       this.configurationOptions.add(configurationOptions);
       return this;
@@ -199,6 +209,7 @@
       return new ConfiguredRuleClassProvider(
           ImmutableMap.copyOf(ruleClassMap),
           ImmutableMap.copyOf(ruleDefinitionMap),
+          ImmutableMap.copyOf(aspectFactoryMap),
           defaultWorkspaceFile.toString(),
           ImmutableList.copyOf(buildInfoFactories),
           ImmutableList.copyOf(configurationOptions),
@@ -247,6 +258,11 @@
   private final ImmutableMap<String, Class<? extends RuleDefinition>> ruleDefinitionMap;
 
   /**
+   * Maps aspect name to the aspect factory meta class.
+   */
+  private final ImmutableMap<String, Class<? extends AspectFactory<?, ?, ?>>> aspectFactoryMap;
+
+  /**
    * The configuration options that affect the behavior of the rules.
    */
   private final ImmutableList<Class<? extends FragmentOptions>> configurationOptions;
@@ -272,6 +288,7 @@
   public ConfiguredRuleClassProvider(
       ImmutableMap<String, RuleClass> ruleClassMap,
       ImmutableMap<String, Class<? extends RuleDefinition>> ruleDefinitionMap,
+      ImmutableMap<String, Class<? extends AspectFactory<?, ?, ?>>> aspectFactoryMap,
       String defaultWorkspaceFile,
       ImmutableList<BuildInfoFactory> buildInfoFactories,
       ImmutableList<Class<? extends FragmentOptions>> configurationOptions,
@@ -282,6 +299,7 @@
 
     this.ruleClassMap = ruleClassMap;
     this.ruleDefinitionMap = ruleDefinitionMap;
+    this.aspectFactoryMap = aspectFactoryMap;
     this.defaultWorkspaceFile = defaultWorkspaceFile;
     this.buildInfoFactories = buildInfoFactories;
     this.configurationOptions = configurationOptions;
@@ -302,6 +320,11 @@
     return ruleClassMap;
   }
 
+  @Override
+  public Map<String, Class<? extends AspectFactory<?, ?, ?>>> getAspectFactoryMap() {
+    return aspectFactoryMap;
+  }
+
   /**
    * Returns a list of build info factories that are needed for the supported languages.
    */
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/OutputGroupProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/OutputGroupProvider.java
index 90b373f..5411f0f 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/OutputGroupProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/OutputGroupProvider.java
@@ -22,6 +22,12 @@
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
 
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
 /**
  * {@code ConfiguredTarget}s implementing this interface can provide artifacts that <b>can</b> be
  * built when the target is mentioned on the command line (as opposed to being always built, like
@@ -93,7 +99,7 @@
 
   private final ImmutableMap<String, NestedSet<Artifact>> outputGroups;
 
-  OutputGroupProvider(ImmutableMap<String, NestedSet<Artifact>> outputGroups) {
+  public OutputGroupProvider(ImmutableMap<String, NestedSet<Artifact>> outputGroups) {
     this.outputGroups = outputGroups;
   }
 
@@ -107,4 +113,32 @@
         ? outputGroups.get(outputGroupName)
         : NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER);
   }
+
+  /**
+   * Merges output groups from two output providers. The set of output groups must be disjoint.
+   *
+   * @param providers providers to merge {@code this} with.
+   */
+  @Nullable
+  public static OutputGroupProvider merge(List<OutputGroupProvider> providers) {
+    if (providers.size() == 0) {
+      return null;
+    }
+    if (providers.size() == 1) {
+      return providers.get(0);
+    }
+
+    ImmutableMap.Builder<String, NestedSet<Artifact>> resultBuilder = new ImmutableMap.Builder<>();
+    Set<String> seenGroups = new HashSet<>();
+    for (OutputGroupProvider provider : providers) {
+      for (String outputGroup : provider.outputGroups.keySet()) {
+        if (!seenGroups.add(outputGroup)) {
+          throw new IllegalStateException("Output group " + outputGroup + " provided twice");
+        }
+
+        resultBuilder.put(outputGroup, provider.getOutputGroup(outputGroup));
+      }
+    }
+    return new OutputGroupProvider(resultBuilder.build());
+  }
 }
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
index 6ba6f1e..fb99467 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTarget.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTarget.java
@@ -27,8 +27,10 @@
 import com.google.devtools.build.lib.packages.Rule;
 import com.google.devtools.build.lib.rules.SkylarkApiProvider;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -127,15 +129,50 @@
     Set<Class<? extends TransitiveInfoProvider>> providers = new HashSet<>();
 
     providers.addAll(base.providers.keySet());
+
+    // Merge output group providers.
+    OutputGroupProvider baseOutputGroupProvider = base.getProvider(OutputGroupProvider.class);
+    List<OutputGroupProvider> outputGroupProviders = new ArrayList<>();
+    if (baseOutputGroupProvider != null) {
+      outputGroupProviders.add(baseOutputGroupProvider);
+    }
+
+    for (Aspect aspect : aspects) {
+      final OutputGroupProvider aspectProvider = aspect.getProvider(OutputGroupProvider.class);
+      if (aspectProvider == null) {
+        continue;
+      }
+      outputGroupProviders.add(aspectProvider);
+    }
+    OutputGroupProvider outputGroupProvider = OutputGroupProvider.merge(outputGroupProviders);
+
+    // Validate that all other providers are only provided once.
     for (Aspect aspect : aspects) {
       for (TransitiveInfoProvider aspectProvider : aspect) {
-        if (!providers.add(aspectProvider.getClass())) {
-          throw new IllegalStateException(
-              "Provider " + aspectProvider.getClass() + " provided twice");
+        Class<? extends TransitiveInfoProvider> aClass = aspectProvider.getClass();
+        if (OutputGroupProvider.class.equals(aClass)) {
+          continue;
+        }
+        if (!providers.add(aClass)) {
+          throw new IllegalStateException("Provider " + aClass + " provided twice");
         }
       }
     }
-    this.providers = base.providers;
+
+    if (baseOutputGroupProvider == outputGroupProvider) {
+      this.providers = base.providers;
+    } else {
+      ImmutableMap.Builder<Class<? extends TransitiveInfoProvider>, Object> builder =
+          new ImmutableMap.Builder<>();
+      for (Class<? extends TransitiveInfoProvider> aClass : base.providers.keySet()) {
+        if (OutputGroupProvider.class.equals(aClass)) {
+          continue;
+        }
+        builder.put(aClass, base.providers.get(aClass));
+      }
+      builder.put(OutputGroupProvider.class, outputGroupProvider);
+      this.providers = builder.build();
+    }
     this.mandatoryStampFiles = base.mandatoryStampFiles;
     this.configConditions = base.configConditions;
     this.aspects = ImmutableList.copyOf(aspects);
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
index ddf3646..043f4e3 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java
@@ -21,6 +21,7 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
 import com.google.devtools.build.lib.rules.test.TestProvider;
+import com.google.devtools.build.lib.skyframe.AspectValue;
 
 /**
  * A small static class containing utility methods for handling the inclusion of
@@ -96,6 +97,22 @@
   }
 
   /**
+   * Utility function to form a NestedSet of all top-level Artifacts of the given targets.
+   */
+  public static ArtifactsToBuild getAllArtifactsToBuildFromAspects(
+      Iterable<AspectValue> aspects, TopLevelArtifactContext context) {
+    NestedSetBuilder<Artifact> allArtifacts = NestedSetBuilder.stableOrder();
+    NestedSetBuilder<Artifact> importantArtifacts = NestedSetBuilder.stableOrder();
+    for (AspectValue aspect : aspects) {
+      ArtifactsToBuild aspectArtifacts = getAllArtifactsToBuild(aspect, context);
+      allArtifacts.addTransitive(aspectArtifacts.getAllArtifacts());
+      importantArtifacts.addTransitive(aspectArtifacts.getImportantArtifacts());
+    }
+    return new ArtifactsToBuild(importantArtifacts.build(), 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 context.compileOnly() == true}, or the files to run.
@@ -138,4 +155,39 @@
     allBuilder.addTransitive(importantArtifacts);
     return new ArtifactsToBuild(importantArtifacts, allBuilder.build());
   }
+
+  public static ArtifactsToBuild getAllArtifactsToBuild(
+      AspectValue aspectValue, TopLevelArtifactContext context) {
+    NestedSetBuilder<Artifact> importantBuilder = NestedSetBuilder.stableOrder();
+    NestedSetBuilder<Artifact> allBuilder = NestedSetBuilder.stableOrder();
+
+    OutputGroupProvider outputGroupProvider =
+        aspectValue.getAspect().getProvider(OutputGroupProvider.class);
+
+    for (String outputGroup : context.outputGroups()) {
+      NestedSetBuilder<Artifact> results = NestedSetBuilder.stableOrder();
+
+      if (outputGroup.equals(OutputGroupProvider.DEFAULT)) {
+        // For the default group, we also throw in filesToBuild
+        FileProvider fileProvider = aspectValue.getAspect().getProvider(FileProvider.class);
+        if (fileProvider != null) {
+          results.addTransitive(fileProvider.getFilesToBuild());
+        }
+      }
+
+      if (outputGroupProvider != null) {
+        results.addTransitive(outputGroupProvider.getOutputGroup(outputGroup));
+      }
+
+      if (outputGroup.startsWith(OutputGroupProvider.HIDDEN_OUTPUT_GROUP_PREFIX)) {
+        allBuilder.addTransitive(results.build());
+      } else {
+        importantBuilder.addTransitive(results.build());
+      }
+    }
+
+    NestedSet<Artifact> importantArtifacts = importantBuilder.build();
+    allBuilder.addTransitive(importantArtifacts);
+    return new ArtifactsToBuild(importantArtifacts, allBuilder.build());
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequest.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequest.java
index f9277a9..a506c44 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequest.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildRequest.java
@@ -81,7 +81,7 @@
   /**
    * Options interface--can be used to parse command-line arguments.
    *
-   * See also ExecutionOptions; from the user's point of view, there's no
+   * <p>See also ExecutionOptions; from the user's point of view, there's no
    * qualitative difference between these two sets of options.
    */
   public static class BuildRequestOptions extends OptionsBase {
@@ -261,6 +261,15 @@
             help = "Check for modifications made to the output files of a build. Consider setting "
                 + "this flag to false to see the effect on incremental build times.")
     public boolean checkOutputFiles;
+
+    @Option(
+      name = "aspects",
+      converter = Converters.CommaSeparatedOptionListConverter.class,
+      defaultValue = "",
+      category = "undocumented", // for now
+      help = "List of top-level aspects"
+    )
+    public List<String> aspects;
   }
 
   /**
@@ -522,6 +531,10 @@
     return ImmutableSortedSet.copyOf(getBuildOptions().multiCpus);
   }
 
+  public ImmutableList<String> getAspects() {
+    return ImmutableList.copyOf(getBuildOptions().aspects);
+  }
+
   public static BuildRequest create(String commandName, OptionsProvider options,
       OptionsProvider startupOptions,
       List<String> targets, OutErr outErr, UUID commandId, long commandStartTime) {
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
index 0fe2562..15fdd4b 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
@@ -460,9 +460,16 @@
     getReporter().handle(Event.progress("Loading complete.  Analyzing..."));
     Profiler.instance().markPhase(ProfilePhase.ANALYZE);
 
-    AnalysisResult analysisResult = getView().update(loadingResult, configurations,
-        request.getViewOptions(), request.getTopLevelArtifactContext(), getReporter(),
-        getEventBus());
+    AnalysisResult analysisResult =
+        getView()
+            .update(
+                loadingResult,
+                configurations,
+                request.getAspects(),
+                request.getViewOptions(),
+                request.getTopLevelArtifactContext(),
+                getReporter(),
+                getEventBus());
 
     // TODO(bazel-team): Merge these into one event.
     getEventBus().post(new AnalysisPhaseCompleteEvent(analysisResult.getTargetsToBuild(),
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
index fbe4140..43ec616 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
@@ -83,6 +83,7 @@
 import com.google.devtools.build.lib.rules.test.TestActionContext;
 import com.google.devtools.build.lib.runtime.BlazeModule;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
+import com.google.devtools.build.lib.skyframe.AspectValue;
 import com.google.devtools.build.lib.skyframe.Builder;
 import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
 import com.google.devtools.build.lib.syntax.Label;
@@ -391,12 +392,20 @@
     Set<ConfiguredTarget> builtTargets = new HashSet<>();
     boolean interrupted = false;
     try {
-      Iterable<Artifact> allArtifactsForProviders = Iterables.concat(
-          additionalArtifacts,
-          TopLevelArtifactHelper.getAllArtifactsToBuild(
-              analysisResult.getTargetsToBuild(), analysisResult.getTopLevelContext())
-              .getAllArtifacts(),
-          TopLevelArtifactHelper.getAllArtifactsToTest(analysisResult.getTargetsToTest()));
+      Collection<AspectValue> aspects = analysisResult.getAspects();
+
+      Iterable<Artifact> allArtifactsForProviders =
+          Iterables.concat(
+              additionalArtifacts,
+              TopLevelArtifactHelper.getAllArtifactsToBuild(
+                      analysisResult.getTargetsToBuild(), analysisResult.getTopLevelContext())
+                  .getAllArtifacts(),
+              TopLevelArtifactHelper.getAllArtifactsToBuildFromAspects(
+                      aspects, analysisResult.getTopLevelContext())
+                  .getAllArtifacts(),
+              //TODO(dslomov): Artifacts to test from aspects?
+              TopLevelArtifactHelper.getAllArtifactsToTest(analysisResult.getTargetsToTest()));
+
       if (request.isRunningInEmacs()) {
         // The syntax of this message is tightly constrained by lisp/progmodes/compile.el in emacs
         request.getOutErr().printErrLn("blaze: Entering directory `" + getExecRoot() + "/'");
@@ -422,11 +431,14 @@
 
       Profiler.instance().markPhase(ProfilePhase.EXECUTE);
 
-      builder.buildArtifacts(additionalArtifacts,
+      builder.buildArtifacts(
+          additionalArtifacts,
           analysisResult.getParallelTests(),
           analysisResult.getExclusiveTests(),
           analysisResult.getTargetsToBuild(),
-          executor, builtTargets,
+          analysisResult.getAspects(),
+          executor,
+          builtTargets,
           request.getBuildOptions().explanationPath != null,
           runtime.getLastExecutionTimeRange());
 
@@ -711,8 +723,8 @@
         }
       }
       if (headerFlag) {
-        outErr.printErr(
-            "Target " + label + " up-to-date (nothing to build)\n");
+        outErr.printErr("Here we are\n");
+        outErr.printErr("Target " + label + " up-to-date (nothing to build)\n");
       }
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/SkyframeBuilder.java b/src/main/java/com/google/devtools/build/lib/buildtool/SkyframeBuilder.java
index d01609d..94c19ef 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/SkyframeBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/SkyframeBuilder.java
@@ -32,6 +32,7 @@
 import com.google.devtools.build.lib.actions.MissingInputFileException;
 import com.google.devtools.build.lib.actions.ResourceManager;
 import com.google.devtools.build.lib.actions.TestExecException;
+import com.google.devtools.build.lib.analysis.AspectCompleteEvent;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.TargetCompleteEvent;
 import com.google.devtools.build.lib.packages.BuildFileNotFoundException;
@@ -39,6 +40,8 @@
 import com.google.devtools.build.lib.runtime.BugReport;
 import com.google.devtools.build.lib.skyframe.ActionExecutionInactivityWatchdog;
 import com.google.devtools.build.lib.skyframe.ActionExecutionValue;
+import com.google.devtools.build.lib.skyframe.AspectCompletionValue;
+import com.google.devtools.build.lib.skyframe.AspectValue;
 import com.google.devtools.build.lib.skyframe.Builder;
 import com.google.devtools.build.lib.skyframe.SkyFunctions;
 import com.google.devtools.build.lib.skyframe.SkyframeActionExecutor;
@@ -90,10 +93,12 @@
   }
 
   @Override
-  public void buildArtifacts(Set<Artifact> artifacts,
+  public void buildArtifacts(
+      Set<Artifact> artifacts,
       Set<ConfiguredTarget> parallelTests,
       Set<ConfiguredTarget> exclusiveTests,
       Collection<ConfiguredTarget> targetsToBuild,
+      Collection<AspectValue> aspects,
       Executor executor,
       Set<ConfiguredTarget> builtTargets,
       boolean explain,
@@ -125,17 +130,32 @@
     watchdog.start();
 
     try {
-      result = skyframeExecutor.buildArtifacts(executor, artifacts, targetsToBuild, parallelTests,
-          /*exclusiveTesting=*/false, keepGoing, explain, numJobs, actionCacheChecker,
-          executionProgressReceiver);
+      result =
+          skyframeExecutor.buildArtifacts(
+              executor,
+              artifacts,
+              targetsToBuild,
+              aspects,
+              parallelTests,
+              /*exclusiveTesting=*/ false,
+              keepGoing,
+              explain,
+              numJobs,
+              actionCacheChecker,
+              executionProgressReceiver);
       // progressReceiver is finished, so unsynchronized access to builtTargets is now safe.
       success = processResult(result, keepGoing, skyframeExecutor);
 
       Preconditions.checkState(
-          !success || result.keyNames().size()
-              == (artifacts.size() + targetsToBuild.size() + parallelTests.size()),
+          !success
+              || result.keyNames().size()
+                  == (artifacts.size()
+                      + targetsToBuild.size()
+                      + aspects.size()
+                      + parallelTests.size()),
           "Build reported as successful but not all artifacts and targets built: %s, %s",
-          result, artifacts);
+          result,
+          artifacts);
 
       // Run exclusive tests: either tagged as "exclusive" or is run in an invocation with
       // --test_output=streamed.
@@ -143,9 +163,19 @@
       for (ConfiguredTarget exclusiveTest : exclusiveTests) {
         // Since only one artifact is being built at a time, we don't worry about an artifact being
         // built and then the build being interrupted.
-        result = skyframeExecutor.buildArtifacts(executor, ImmutableSet.<Artifact>of(),
-            targetsToBuild, ImmutableSet.of(exclusiveTest), /*exclusiveTesting=*/true, keepGoing,
-            explain, numJobs, actionCacheChecker, null);
+        result =
+            skyframeExecutor.buildArtifacts(
+                executor,
+                ImmutableSet.<Artifact>of(),
+                targetsToBuild,
+                aspects,
+                ImmutableSet.of(exclusiveTest), /*exclusiveTesting=*/
+                true,
+                keepGoing,
+                explain,
+                numJobs,
+                actionCacheChecker,
+                null);
         boolean exclusiveSuccess = processResult(result, keepGoing, skyframeExecutor);
         Preconditions.checkState(!exclusiveSuccess || !result.keyNames().isEmpty(),
             "Build reported as successful but test %s not executed: %s",
@@ -177,7 +207,7 @@
   /**
    * Process the Skyframe update, taking into account the keepGoing setting.
    *
-   * Returns false if the update() failed, but we should continue. Returns true on success.
+   * <p>Returns false if the update() failed, but we should continue. Returns true on success.
    * Throws on fail-fast failures.
    */
   private static boolean processResult(EvaluationResult<?> result, boolean keepGoing,
@@ -305,6 +335,9 @@
         ConfiguredTarget target = val.getConfiguredTarget();
         builtTargets.add(target);
         eventBus.post(TargetCompleteEvent.createSuccessful(target));
+      } else if (type == SkyFunctions.ASPECT_COMPLETION && node != null) {
+        AspectCompletionValue val = (AspectCompletionValue) node;
+        eventBus.post(AspectCompleteEvent.createSuccessful(val.getAspectValue()));
       } else if (type == SkyFunctions.ACTION_EXECUTION) {
         // Remember all completed actions, even those in error, regardless of having been cached or
         // really executed.
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/packages/RuleClassProvider.java
index 6781d56..3f24efb 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleClassProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleClassProvider.java
@@ -30,6 +30,11 @@
   Map<String, RuleClass> getRuleClassMap();
 
   /**
+   * Returns a map from aspect names to aspect factory objects.
+   */
+  Map<String, Class<? extends AspectFactory<?, ?, ?>>> getAspectFactoryMap();
+
+  /**
    * Returns a new Skylark Environment instance for rule creation. Implementations need to be
    * thread safe.
    */
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidNeverlinkAspect.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidNeverlinkAspect.java
index 189b6fb..92207e8 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidNeverlinkAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidNeverlinkAspect.java
@@ -38,14 +38,16 @@
  * ijars,
  */
 public class AndroidNeverlinkAspect implements ConfiguredAspectFactory {
-  private static final ImmutableList<String> ATTRIBUTES = ImmutableList.of(
-      "deps", "exports", "runtime_deps", "binary_under_test", "$instrumentation_test_runner");
+  public static final String NAME = "AndroidNeverlinkAspect";
+  private static final ImmutableList<String> ATTRIBUTES =
+      ImmutableList.of(
+          "deps", "exports", "runtime_deps", "binary_under_test", "$instrumentation_test_runner");
 
   @Override
   public Aspect create(ConfiguredTarget base, RuleContext ruleContext) {
     if (!JavaCommon.getConstraints(ruleContext).contains("android")
         && !ruleContext.getRule().getRuleClass().startsWith("android_")) {
-      return new Aspect.Builder().build();
+      return new Aspect.Builder(NAME).build();
     }
 
     List<TransitiveInfoCollection> deps = new ArrayList<>();
@@ -61,12 +63,14 @@
       deps.addAll(ruleContext.getPrerequisites(attribute, Mode.TARGET));
     }
 
-    return new Aspect.Builder()
-        .addProvider(AndroidNeverLinkLibrariesProvider.class,
-            new AndroidNeverLinkLibrariesProvider(AndroidCommon.collectTransitiveNeverlinkLibraries(
-                ruleContext,
-                deps,
-                base.getProvider(JavaRuntimeJarProvider.class).getRuntimeJars())))
+    return new Aspect.Builder(NAME)
+        .addProvider(
+            AndroidNeverLinkLibrariesProvider.class,
+            new AndroidNeverLinkLibrariesProvider(
+                AndroidCommon.collectTransitiveNeverlinkLibraries(
+                    ruleContext,
+                    deps,
+                    base.getProvider(JavaRuntimeJarProvider.class).getRuntimeJars())))
         .build();
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcAspect.java b/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcAspect.java
index 99bdfb4..669dc4eb 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcAspect.java
@@ -50,6 +50,7 @@
  * J2ObjC transpilation aspect for Java rules.
  */
 public class J2ObjcAspect implements ConfiguredAspectFactory {
+  public static final String NAME = "J2ObjcAspect";
   /**
    * Adds the attribute aspect args to the given AspectDefinition.Builder.
    */
@@ -85,7 +86,7 @@
 
   @Override
   public Aspect create(ConfiguredTarget base, RuleContext ruleContext) {
-    Aspect.Builder builder = new Aspect.Builder();
+    Aspect.Builder builder = new Aspect.Builder(NAME);
 
     JavaCompilationArgsProvider compilationArgsProvider =
         base.getProvider(JavaCompilationArgsProvider.class);
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/AspectCompletionValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/AspectCompletionValue.java
new file mode 100644
index 0000000..695bc39
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/AspectCompletionValue.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.skyframe;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+
+import java.util.Collection;
+
+/**
+ * The value of an AspectCompletion. Currently this just stores an Aspect.
+ */
+public class AspectCompletionValue implements SkyValue {
+  private final AspectValue aspectValue;
+
+  AspectCompletionValue(AspectValue aspectValue) {
+    this.aspectValue = aspectValue;
+  }
+
+  public AspectValue getAspectValue() {
+    return aspectValue;
+  }
+
+  public static Iterable<SkyKey> keys(Collection<AspectValue> targets) {
+    return Iterables.transform(
+        targets,
+        new Function<AspectValue, SkyKey>() {
+          @Override
+          public SkyKey apply(AspectValue aspectValue) {
+            return new SkyKey(SkyFunctions.ASPECT_COMPLETION, aspectValue.getKey());
+          }
+        });
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
index 30358ac..7db3e46 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
@@ -80,9 +80,14 @@
           "aspects must be attached to rules"));
     }
 
-    RuleConfiguredTarget associatedTarget = (RuleConfiguredTarget)
-        ((ConfiguredTargetValue) env.getValue(ConfiguredTargetValue.key(
-            key.getLabel(), key.getConfiguration()))).getConfiguredTarget();
+    final ConfiguredTargetValue configuredTargetValue =
+        (ConfiguredTargetValue)
+            env.getValue(ConfiguredTargetValue.key(key.getLabel(), key.getConfiguration()));
+    if (configuredTargetValue == null) {
+      return null;
+    }
+    RuleConfiguredTarget associatedTarget =
+        (RuleConfiguredTarget) configuredTargetValue.getConfiguredTarget();
 
     if (associatedTarget == null) {
       return null;
@@ -152,7 +157,11 @@
     Preconditions.checkNotNull(aspect);
 
     return new AspectValue(
-        aspect, ImmutableList.copyOf(analysisEnvironment.getRegisteredActions()));
+        key,
+        associatedTarget.getLabel(),
+        associatedTarget.getTarget().getLocation(),
+        aspect,
+        ImmutableList.copyOf(analysisEnvironment.getRegisteredActions()));
   }
 
   @Nullable
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/AspectValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/AspectValue.java
index b8a6027..2657caa 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/AspectValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/AspectValue.java
@@ -19,6 +19,7 @@
 import com.google.devtools.build.lib.analysis.Aspect;
 import com.google.devtools.build.lib.analysis.ConfiguredAspectFactory;
 import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.events.Location;
 import com.google.devtools.build.lib.syntax.Label;
 import com.google.devtools.build.skyframe.SkyFunctionName;
 import com.google.devtools.build.skyframe.SkyKey;
@@ -91,19 +92,49 @@
     }
   }
 
+  private final Label label;
+  private final Location location;
+  private final AspectKey key;
   private final Aspect aspect;
 
-  public AspectValue(Aspect aspect, Iterable<Action> actions) {
+  public AspectValue(
+      AspectKey key, Label label, Location location, Aspect aspect, Iterable<Action> actions) {
     super(actions);
+    this.location = location;
+    this.label = label;
+    this.key = key;
     this.aspect = aspect;
   }
 
-  public Aspect get() {
+  public Aspect getAspect() {
     return aspect;
   }
 
+  public Label getLabel() {
+    return label;
+  }
+
+  public Location getLocation() {
+    return location;
+  }
+
+  public AspectKey getKey() {
+    return key;
+  }
+
   public static SkyKey key(Label label, BuildConfiguration configuration,
       Class<? extends ConfiguredAspectFactory> aspectFactory) {
     return new SkyKey(SkyFunctions.ASPECT, new AspectKey(label, configuration, aspectFactory));
   }
+
+  public static SkyKey key(AspectKey aspectKey) {
+    return new SkyKey(SkyFunctions.ASPECT, aspectKey);
+  }
+
+  public static AspectKey createAspectKey(
+      Label label,
+      BuildConfiguration configuration,
+      Class<? extends ConfiguredAspectFactory> aspectFactory) {
+    return new AspectKey(label, configuration, aspectFactory);
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/Builder.java b/src/main/java/com/google/devtools/build/lib/skyframe/Builder.java
index f23b09f..67e32b1 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/Builder.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/Builder.java
@@ -54,6 +54,7 @@
    *        artifacts.
    * @param exclusiveTests are executed one at a time, only after all other tasks have completed
    * @param targetsToBuild Set of targets which will be built
+   * @param aspects Set of aspects that will be built
    * @param executor an opaque application-specific value that will be
    *        passed down to the execute() method of any Action executed during
    *        this call
@@ -69,10 +70,12 @@
    * @throws TestExecException if any test fails
    */
   @ThreadCompatible
-  void buildArtifacts(Set<Artifact> artifacts,
+  void buildArtifacts(
+      Set<Artifact> artifacts,
       Set<ConfiguredTarget> parallelTests,
       Set<ConfiguredTarget> exclusiveTests,
       Collection<ConfiguredTarget> targetsToBuild,
+      Collection<AspectValue> aspects,
       Executor executor,
       Set<ConfiguredTarget> builtTargets,
       boolean explain,
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/CompletionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/CompletionFunction.java
new file mode 100644
index 0000000..80eb624
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/CompletionFunction.java
@@ -0,0 +1,275 @@
+// 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.skyframe;
+
+import com.google.common.eventbus.EventBus;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.MissingInputFileException;
+import com.google.devtools.build.lib.analysis.AspectCompleteEvent;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.LabelAndConfiguration;
+import com.google.devtools.build.lib.analysis.TargetCompleteEvent;
+import com.google.devtools.build.lib.analysis.TopLevelArtifactContext;
+import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper;
+import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper.ArtifactsToBuild;
+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.skyframe.AspectValue.AspectKey;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyFunctionException;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+import com.google.devtools.build.skyframe.ValueOrException2;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.annotation.Nullable;
+
+/**
+ * CompletionFunction builds the artifactsToBuild collection of a {@link ConfiguredTarget}.
+ */
+public final class CompletionFunction<TValue extends SkyValue, TResult extends SkyValue>
+    implements SkyFunction {
+
+  /**
+   * A strategy for completing the build.
+   */
+  public interface Completor<TValue, TResult extends SkyValue> {
+
+    /**
+     * Obtains an analysis result value from environment.
+     */
+    TValue getValueFromSkyKey(SkyKey skyKey, Environment env);
+
+    /**
+     * Returns all artefacts that need to be built to complete the {@code value}
+     */
+    ArtifactsToBuild getAllArtifactsToBuild(TValue value, TopLevelArtifactContext context);
+
+    /**
+     * Creates an event reporting an absent input artifact.
+     */
+    Event getRootCauseError(TValue value, Label rootCause);
+
+    /**
+     * Creates an error message reporting {@code missingCount} missing input files.
+     */
+    MissingInputFileException getMissingFilesException(TValue value, int missingCount);
+
+    /**
+     * Creates a successful completion value.
+     */
+    TResult createResult(TValue value);
+
+    /**
+     * Creates a failed completion value.
+     */
+    SkyValue createFailed(TValue value, NestedSet<Label> rootCauses);
+  }
+
+  private static class TargetCompletor
+      implements Completor<ConfiguredTargetValue, TargetCompletionValue> {
+    @Override
+    public ConfiguredTargetValue getValueFromSkyKey(SkyKey skyKey, Environment env) {
+      LabelAndConfiguration lac = (LabelAndConfiguration) skyKey.argument();
+      return (ConfiguredTargetValue)
+          env.getValue(ConfiguredTargetValue.key(lac.getLabel(), lac.getConfiguration()));
+    }
+
+    @Override
+    public ArtifactsToBuild getAllArtifactsToBuild(
+        ConfiguredTargetValue value, TopLevelArtifactContext topLevelContext) {
+      return TopLevelArtifactHelper.getAllArtifactsToBuild(
+          value.getConfiguredTarget(), topLevelContext);
+    }
+
+    @Override
+    public Event getRootCauseError(ConfiguredTargetValue ctValue, Label rootCause) {
+      return Event.error(
+          ctValue.getConfiguredTarget().getTarget().getLocation(),
+          String.format(
+              "%s: missing input file '%s'", ctValue.getConfiguredTarget().getLabel(), rootCause));
+    }
+
+    @Override
+    public MissingInputFileException getMissingFilesException(
+        ConfiguredTargetValue value, int missingCount) {
+      return new MissingInputFileException(
+          value.getConfiguredTarget().getTarget().getLocation()
+              + " "
+              + missingCount
+              + " input file(s) do not exist",
+          value.getConfiguredTarget().getTarget().getLocation());
+    }
+
+    @Override
+    public TargetCompletionValue createResult(ConfiguredTargetValue value) {
+      return new TargetCompletionValue(value.getConfiguredTarget());
+    }
+
+    @Override
+    public SkyValue createFailed(ConfiguredTargetValue value, NestedSet<Label> rootCauses) {
+      return TargetCompleteEvent.createFailed(value.getConfiguredTarget(), rootCauses);
+    }
+  }
+
+  private static class AspectCompletor implements Completor<AspectValue, AspectCompletionValue> {
+    @Override
+    public AspectValue getValueFromSkyKey(SkyKey skyKey, Environment env) {
+      AspectKey aspectKey = (AspectKey) skyKey.argument();
+      return (AspectValue) env.getValue(AspectValue.key(aspectKey));
+    }
+
+    @Override
+    public ArtifactsToBuild getAllArtifactsToBuild(
+        AspectValue value, TopLevelArtifactContext topLevelArtifactContext) {
+      return TopLevelArtifactHelper.getAllArtifactsToBuild(value, topLevelArtifactContext);
+    }
+
+    @Override
+    public Event getRootCauseError(AspectValue value, Label rootCause) {
+      return Event.error(
+          value.getLocation(),
+          String.format(
+              "%s, aspect %s: missing input file '%s'",
+              value.getLabel(),
+              value.getAspect().getName(),
+              rootCause));
+    }
+
+    @Override
+    public MissingInputFileException getMissingFilesException(AspectValue value, int missingCount) {
+      return new MissingInputFileException(
+          value.getLabel()
+              + ", aspect "
+              + value.getAspect().getName()
+              + missingCount
+              + " input file(s) do not exist",
+          value.getLocation());
+    }
+
+    @Override
+    public AspectCompletionValue createResult(AspectValue value) {
+      return new AspectCompletionValue(value);
+    }
+
+    @Override
+    public SkyValue createFailed(AspectValue value, NestedSet<Label> rootCauses) {
+      return AspectCompleteEvent.createFailed(value, rootCauses);
+    }
+  }
+
+  public static SkyFunction targetCompletionFunction(AtomicReference<EventBus> eventBusRef) {
+    return new CompletionFunction<>(eventBusRef, new TargetCompletor());
+  }
+
+  public static SkyFunction aspectCompletionFunction(AtomicReference<EventBus> eventBusRef) {
+    return new CompletionFunction<>(eventBusRef, new AspectCompletor());
+  }
+
+  private final AtomicReference<EventBus> eventBusRef;
+  private final Completor<TValue, TResult> completor;
+
+  private CompletionFunction(
+      AtomicReference<EventBus> eventBusRef, Completor<TValue, TResult> completor) {
+    this.eventBusRef = eventBusRef;
+    this.completor = completor;
+  }
+
+  @Nullable
+  @Override
+  public SkyValue compute(SkyKey skyKey, Environment env) throws CompletionFunctionException {
+    TValue value = completor.getValueFromSkyKey(skyKey, env);
+    TopLevelArtifactContext topLevelContext = PrecomputedValue.TOP_LEVEL_CONTEXT.get(env);
+    if (env.valuesMissing()) {
+      return null;
+    }
+
+    Map<SkyKey, ValueOrException2<MissingInputFileException, ActionExecutionException>> inputDeps =
+        env.getValuesOrThrow(
+            ArtifactValue.mandatoryKeys(
+                completor.getAllArtifactsToBuild(value, topLevelContext).getAllArtifacts()),
+            MissingInputFileException.class,
+            ActionExecutionException.class);
+
+    int missingCount = 0;
+    ActionExecutionException firstActionExecutionException = null;
+    MissingInputFileException missingInputException = null;
+    NestedSetBuilder<Label> rootCausesBuilder = NestedSetBuilder.stableOrder();
+    for (Map.Entry<SkyKey, ValueOrException2<MissingInputFileException, ActionExecutionException>>
+        depsEntry : inputDeps.entrySet()) {
+      Artifact input = ArtifactValue.artifact(depsEntry.getKey());
+      try {
+        depsEntry.getValue().get();
+      } catch (MissingInputFileException e) {
+        missingCount++;
+        final Label inputOwner = input.getOwner();
+        if (inputOwner != null) {
+          rootCausesBuilder.add(inputOwner);
+          env.getListener().handle(completor.getRootCauseError(value, inputOwner));
+        }
+      } catch (ActionExecutionException e) {
+        rootCausesBuilder.addTransitive(e.getRootCauses());
+        if (firstActionExecutionException == null) {
+          firstActionExecutionException = e;
+        }
+      }
+    }
+
+    if (missingCount > 0) {
+      missingInputException = completor.getMissingFilesException(value, missingCount);
+    }
+
+    NestedSet<Label> rootCauses = rootCausesBuilder.build();
+    if (!rootCauses.isEmpty()) {
+      eventBusRef.get().post(completor.createFailed(value, rootCauses));
+      if (firstActionExecutionException != null) {
+        throw new CompletionFunctionException(firstActionExecutionException);
+      } else {
+        throw new CompletionFunctionException(missingInputException);
+      }
+    }
+
+    return env.valuesMissing() ? null : completor.createResult(value);
+  }
+
+  @Override
+  public String extractTag(SkyKey skyKey) {
+    return Label.print(((LabelAndConfiguration) skyKey.argument()).getLabel());
+  }
+
+  private static final class CompletionFunctionException extends SkyFunctionException {
+
+    private final ActionExecutionException actionException;
+
+    public CompletionFunctionException(ActionExecutionException e) {
+      super(e, Transience.PERSISTENT);
+      this.actionException = e;
+    }
+
+    public CompletionFunctionException(MissingInputFileException e) {
+      super(e, Transience.TRANSIENT);
+      this.actionException = null;
+    }
+
+    @Override
+    public boolean isCatastrophic() {
+      return actionException != null && actionException.isCatastrophe();
+    }
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
index dff91d6..1132a7b 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ConfiguredTargetFunction.java
@@ -309,7 +309,7 @@
           // Dependent aspect has either not been computed yet or is in error.
           return null;
         }
-        result.put(depKey, aspectValue.get());
+        result.put(depKey, aspectValue.getAspect());
       }
     }
 
@@ -320,7 +320,7 @@
       Class<? extends ConfiguredAspectFactory> aspectFactory) {
     AspectDefinition aspectDefinition = AspectFactory.Util.create(aspectFactory).getDefinition();
     for (Class<?> provider : aspectDefinition.getRequiredProviders()) {
-      if (dep.getProvider((Class<? extends TransitiveInfoProvider>) provider) == null) {
+      if (dep.getProvider(provider.asSubclass(TransitiveInfoProvider.class)) == null) {
         return false;
       }
     }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java
index caec042..aba4e49 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java
@@ -60,8 +60,9 @@
       SkyFunctionName.create("POST_CONFIGURED_TARGET");
   public static final SkyFunctionName TARGET_COMPLETION =
       SkyFunctionName.create("TARGET_COMPLETION");
-  public static final SkyFunctionName TEST_COMPLETION =
-      SkyFunctionName.create("TEST_COMPLETION");
+  public static final SkyFunctionName ASPECT_COMPLETION =
+      SkyFunctionName.create("ASPECT_COMPLETION");
+  public static final SkyFunctionName TEST_COMPLETION = SkyFunctionName.create("TEST_COMPLETION");
   public static final SkyFunctionName BUILD_CONFIGURATION =
       SkyFunctionName.create("BUILD_CONFIGURATION");
   public static final SkyFunctionName CONFIGURATION_FRAGMENT =
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeAnalysisResult.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeAnalysisResult.java
new file mode 100644
index 0000000..323dc3d
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeAnalysisResult.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.skyframe;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.skyframe.WalkableGraph;
+
+import java.util.Collection;
+
+/**
+ *  Encapsulates the raw analysis result of top level targets and aspects coming from Skyframe.
+ */
+public class SkyframeAnalysisResult {
+  private final ImmutableList<ConfiguredTarget> configuredTargets;
+  private final WalkableGraph walkableGraph;
+  private final ImmutableList<AspectValue> aspects;
+
+  public SkyframeAnalysisResult(
+      ImmutableList<ConfiguredTarget> configuredTargets,
+      WalkableGraph walkableGraph,
+      ImmutableList<AspectValue> aspects) {
+    this.configuredTargets = configuredTargets;
+    this.walkableGraph = walkableGraph;
+    this.aspects = aspects;
+  }
+
+  public Collection<ConfiguredTarget> getConfiguredTargets() {
+    return configuredTargets;
+  }
+
+  public WalkableGraph getWalkableGraph() {
+    return walkableGraph;
+  }
+
+  public Collection<AspectValue> getAspects() {
+    return aspects;
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java
index ed85621..eed73ae 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java
@@ -14,6 +14,7 @@
 package com.google.devtools.build.lib.skyframe;
 
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
@@ -48,11 +49,11 @@
 import com.google.devtools.build.lib.packages.Attribute;
 import com.google.devtools.build.lib.packages.Target;
 import com.google.devtools.build.lib.skyframe.ActionLookupValue.ActionLookupKey;
+import com.google.devtools.build.lib.skyframe.AspectValue.AspectKey;
 import com.google.devtools.build.lib.skyframe.BuildInfoCollectionValue.BuildInfoKeyAndConfig;
 import com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction.ConfiguredValueCreationException;
 import com.google.devtools.build.lib.skyframe.SkyframeActionExecutor.ConflictException;
 import com.google.devtools.build.lib.syntax.Label;
-import com.google.devtools.build.lib.util.Pair;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.skyframe.CycleInfo;
 import com.google.devtools.build.skyframe.ErrorInfo;
@@ -61,7 +62,6 @@
 import com.google.devtools.build.skyframe.SkyFunction.Environment;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
-import com.google.devtools.build.skyframe.WalkableGraph;
 
 import java.util.Collection;
 import java.util.HashSet;
@@ -160,33 +160,51 @@
    *
    * @return the configured targets that should be built along with a WalkableGraph of the analysis.
    */
-  public Pair<Collection<ConfiguredTarget>, WalkableGraph> configureTargets(
-      List<ConfiguredTargetKey> values, EventBus eventBus, boolean keepGoing)
-          throws InterruptedException, ViewCreationFailedException {
+  public SkyframeAnalysisResult configureTargets(
+      List<ConfiguredTargetKey> values,
+      List<AspectKey> aspectKeys,
+      EventBus eventBus,
+      boolean keepGoing)
+      throws InterruptedException, ViewCreationFailedException {
     enableAnalysis(true);
-    EvaluationResult<ConfiguredTargetValue> result;
+    EvaluationResult<ActionLookupValue> result;
     try {
-      result = skyframeExecutor.configureTargets(values, keepGoing);
+      result = skyframeExecutor.configureTargets(values, aspectKeys, keepGoing);
     } finally {
       enableAnalysis(false);
     }
     ImmutableMap<Action, ConflictException> badActions = skyframeExecutor.findArtifactConflicts();
 
+    Collection<AspectValue> goodAspects = Lists.newArrayListWithCapacity(values.size());
+    for (AspectKey aspectKey : aspectKeys) {
+      AspectValue value = (AspectValue) result.get(AspectValue.key(aspectKey));
+      if (value == null) {
+        // Skip aspects that couldn't be applied to targets.
+        continue;
+      }
+      goodAspects.add(value);
+    }
+
     // Filter out all CTs that have a bad action and convert to a list of configured targets. This
     // code ensures that the resulting list of configured targets has the same order as the incoming
     // list of values, i.e., that the order is deterministic.
     Collection<ConfiguredTarget> goodCts = Lists.newArrayListWithCapacity(values.size());
     for (ConfiguredTargetKey value : values) {
-      ConfiguredTargetValue ctValue = result.get(ConfiguredTargetValue.key(value));
+      ConfiguredTargetValue ctValue =
+          (ConfiguredTargetValue) result.get(ConfiguredTargetValue.key(value));
       if (ctValue == null) {
         continue;
       }
       goodCts.add(ctValue.getConfiguredTarget());
     }
 
+
     if (!result.hasError() && badActions.isEmpty()) {
       setDeserializedArtifactOwners();
-      return Pair.of(goodCts, result.getWalkableGraph());
+      return new SkyframeAnalysisResult(
+          ImmutableList.copyOf(goodCts),
+          result.getWalkableGraph(),
+          ImmutableList.copyOf(goodAspects));
     }
 
     // --nokeep_going so we fail with an exception for the first error.
@@ -291,7 +309,10 @@
       }
     }
     setDeserializedArtifactOwners();
-    return Pair.of(goodCts, result.getWalkableGraph());
+    return new SkyframeAnalysisResult(
+        ImmutableList.copyOf(goodCts),
+        result.getWalkableGraph(),
+        ImmutableList.copyOf(goodAspects));
   }
 
   @Nullable
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index 8b482e5..cbcc676 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -83,6 +83,7 @@
 import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
 import com.google.devtools.build.lib.pkgcache.TransitivePackageLoader;
 import com.google.devtools.build.lib.profiler.Profiler;
+import com.google.devtools.build.lib.skyframe.AspectValue.AspectKey;
 import com.google.devtools.build.lib.skyframe.DirtinessCheckerUtils.FileDirtinessChecker;
 import com.google.devtools.build.lib.skyframe.SkyframeActionExecutor.ActionCompletedReceiver;
 import com.google.devtools.build.lib.skyframe.SkyframeActionExecutor.ProgressSupplier;
@@ -338,7 +339,8 @@
     map.put(
         SkyFunctions.WORKSPACE_FILE,
         new WorkspaceFileFunction(ruleClassProvider, pkgFactory, directories));
-    map.put(SkyFunctions.TARGET_COMPLETION, new TargetCompletionFunction(eventBus));
+    map.put(SkyFunctions.TARGET_COMPLETION, CompletionFunction.targetCompletionFunction(eventBus));
+    map.put(SkyFunctions.ASPECT_COMPLETION, CompletionFunction.aspectCompletionFunction(eventBus));
     map.put(SkyFunctions.TEST_COMPLETION, new TestCompletionFunction());
     map.put(SkyFunctions.ARTIFACT, new ArtifactFunction(allowedMissingInputs));
     map.put(SkyFunctions.BUILD_INFO_COLLECTION, new BuildInfoCollectionFunction(artifactFactory,
@@ -1003,13 +1005,15 @@
       Executor executor,
       Set<Artifact> artifactsToBuild,
       Collection<ConfiguredTarget> targetsToBuild,
+      Collection<AspectValue> aspects,
       Collection<ConfiguredTarget> targetsToTest,
       boolean exclusiveTesting,
       boolean keepGoing,
       boolean explain,
       int numJobs,
       ActionCacheChecker actionCacheChecker,
-      @Nullable EvaluationProgressReceiver executionProgressReceiver) throws InterruptedException {
+      @Nullable EvaluationProgressReceiver executionProgressReceiver)
+      throws InterruptedException {
     checkActive();
     Preconditions.checkState(actionLogBufferPathGenerator != null);
 
@@ -1020,9 +1024,13 @@
       progressReceiver.executionProgressReceiver = executionProgressReceiver;
       Iterable<SkyKey> artifactKeys = ArtifactValue.mandatoryKeys(artifactsToBuild);
       Iterable<SkyKey> targetKeys = TargetCompletionValue.keys(targetsToBuild);
+      Iterable<SkyKey> aspectKeys = AspectCompletionValue.keys(aspects);
       Iterable<SkyKey> testKeys = TestCompletionValue.keys(targetsToTest, exclusiveTesting);
-      return buildDriver.evaluate(Iterables.concat(artifactKeys, targetKeys, testKeys), keepGoing,
-          numJobs, errorEventListener);
+      return buildDriver.evaluate(
+          Iterables.concat(artifactKeys, targetKeys, aspectKeys, testKeys),
+          keepGoing,
+          numJobs,
+          errorEventListener);
     } finally {
       progressReceiver.executionProgressReceiver = null;
       // Also releases thread locks.
@@ -1108,7 +1116,7 @@
           continue DependentNodeLoop;
         }
 
-        aspects.add(((AspectValue) result.get(aspectKey)).get());
+        aspects.add(((AspectValue) result.get(aspectKey)).getAspect());
       }
 
       cts.add(RuleConfiguredTarget.mergeAspects(configuredTarget, aspects));
@@ -1157,13 +1165,20 @@
   /**
    * Configures a given set of configured targets.
    */
-  public EvaluationResult<ConfiguredTargetValue> configureTargets(
-      List<ConfiguredTargetKey> values, boolean keepGoing) throws InterruptedException {
+  public EvaluationResult<ActionLookupValue> configureTargets(
+      List<ConfiguredTargetKey> values, List<AspectKey> aspectKeys, boolean keepGoing)
+      throws InterruptedException {
     checkActive();
 
+    Set<SkyKey> keys = new HashSet<>();
+    keys.addAll(ConfiguredTargetValue.keys(values));
+    for (AspectKey aspectKey : aspectKeys) {
+      keys.add(AspectValue.key(aspectKey));
+    }
+
     // Make sure to not run too many analysis threads. This can cause memory thrashing.
-    return buildDriver.evaluate(ConfiguredTargetValue.keys(values), keepGoing,
-        ResourceUsage.getAvailableProcessors(), errorEventListener);
+    return buildDriver.evaluate(
+        keys, keepGoing, ResourceUsage.getAvailableProcessors(), errorEventListener);
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TargetCompletionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/TargetCompletionFunction.java
deleted file mode 100644
index e89c0fc..0000000
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TargetCompletionFunction.java
+++ /dev/null
@@ -1,138 +0,0 @@
-// 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.skyframe;
-
-import com.google.common.eventbus.EventBus;
-import com.google.devtools.build.lib.actions.ActionExecutionException;
-import com.google.devtools.build.lib.actions.Artifact;
-import com.google.devtools.build.lib.actions.MissingInputFileException;
-import com.google.devtools.build.lib.analysis.ConfiguredTarget;
-import com.google.devtools.build.lib.analysis.LabelAndConfiguration;
-import com.google.devtools.build.lib.analysis.TargetCompleteEvent;
-import com.google.devtools.build.lib.analysis.TopLevelArtifactContext;
-import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper;
-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.syntax.Label;
-import com.google.devtools.build.skyframe.SkyFunction;
-import com.google.devtools.build.skyframe.SkyFunctionException;
-import com.google.devtools.build.skyframe.SkyKey;
-import com.google.devtools.build.skyframe.SkyValue;
-import com.google.devtools.build.skyframe.ValueOrException2;
-
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicReference;
-
-import javax.annotation.Nullable;
-
-/**
- * TargetCompletionFunction builds the artifactsToBuild collection of a {@link ConfiguredTarget}.
- */
-public final class TargetCompletionFunction implements SkyFunction {
-
-  private final AtomicReference<EventBus> eventBusRef;
-
-  public TargetCompletionFunction(AtomicReference<EventBus> eventBusRef) {
-    this.eventBusRef = eventBusRef;
-  }
-
-  @Nullable
-  @Override
-  public SkyValue compute(SkyKey skyKey, Environment env) throws TargetCompletionFunctionException {
-    LabelAndConfiguration lac = (LabelAndConfiguration) skyKey.argument();
-    ConfiguredTargetValue ctValue = (ConfiguredTargetValue)
-        env.getValue(ConfiguredTargetValue.key(lac.getLabel(), lac.getConfiguration()));
-    TopLevelArtifactContext topLevelContext = PrecomputedValue.TOP_LEVEL_CONTEXT.get(env);
-    if (env.valuesMissing()) {
-      return null;
-    }
-
-    Map<SkyKey, ValueOrException2<MissingInputFileException, ActionExecutionException>> inputDeps =
-        env.getValuesOrThrow(ArtifactValue.mandatoryKeys(
-            TopLevelArtifactHelper.getAllArtifactsToBuild(
-                ctValue.getConfiguredTarget(), topLevelContext).getAllArtifacts()),
-            MissingInputFileException.class, ActionExecutionException.class);
-
-    int missingCount = 0;
-    ActionExecutionException firstActionExecutionException = null;
-    MissingInputFileException missingInputException = null;
-    NestedSetBuilder<Label> rootCausesBuilder = NestedSetBuilder.stableOrder();
-    for (Map.Entry<SkyKey, ValueOrException2<MissingInputFileException,
-        ActionExecutionException>> depsEntry : inputDeps.entrySet()) {
-      Artifact input = ArtifactValue.artifact(depsEntry.getKey());
-      try {
-        depsEntry.getValue().get();
-      } catch (MissingInputFileException e) {
-        missingCount++;
-        if (input.getOwner() != null) {
-          rootCausesBuilder.add(input.getOwner());
-          env.getListener().handle(Event.error(
-              ctValue.getConfiguredTarget().getTarget().getLocation(),
-              String.format("%s: missing input file '%s'",
-                  lac.getLabel(), input.getOwner())));
-        }
-      } catch (ActionExecutionException e) {
-        rootCausesBuilder.addTransitive(e.getRootCauses());
-        if (firstActionExecutionException == null) {
-          firstActionExecutionException = e;
-        }
-      }
-    }
-
-    if (missingCount > 0) {
-      missingInputException = new MissingInputFileException(
-          ctValue.getConfiguredTarget().getTarget().getLocation() + " " + missingCount
-          + " input file(s) do not exist", ctValue.getConfiguredTarget().getTarget().getLocation());
-    }
-
-    NestedSet<Label> rootCauses = rootCausesBuilder.build();
-    if (!rootCauses.isEmpty()) {
-      eventBusRef.get().post(
-          TargetCompleteEvent.createFailed(ctValue.getConfiguredTarget(), rootCauses));
-      if (firstActionExecutionException != null) {
-        throw new TargetCompletionFunctionException(firstActionExecutionException);
-      } else {
-        throw new TargetCompletionFunctionException(missingInputException);
-      }
-    }
-
-    return env.valuesMissing() ? null : new TargetCompletionValue(ctValue.getConfiguredTarget());
-  }
-
-  @Override
-  public String extractTag(SkyKey skyKey) {
-    return Label.print(((LabelAndConfiguration) skyKey.argument()).getLabel());
-  }
-
-  private static final class TargetCompletionFunctionException extends SkyFunctionException {
-
-    private final ActionExecutionException actionException;
-
-    public TargetCompletionFunctionException(ActionExecutionException e) {
-      super(e, Transience.PERSISTENT);
-      this.actionException = e;
-    }
-
-    public TargetCompletionFunctionException(MissingInputFileException e) {
-      super(e, Transience.TRANSIENT);
-      this.actionException = null;
-    }
-
-    @Override
-    public boolean isCatastrophic() {
-      return actionException != null && actionException.isCatastrophe();
-    }
-  }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TestCompletionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/TestCompletionFunction.java
index b6f9606..35abad2 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TestCompletionFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TestCompletionFunction.java
@@ -23,7 +23,7 @@
 import com.google.devtools.build.skyframe.SkyValue;
 
 /**
- * TargetCompletionFunction builds all relevant test artifacts of a {@link
+ * TestCompletionFunction builds all relevant test artifacts of a {@link
  * com.google.devtools.build.lib.analysis.ConfiguredTarget}. This includes test shards and repeated
  * runs.
  */
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
index ad53743..59a74a8 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
@@ -263,8 +263,15 @@
     ImmutableSortedSet<String> multiCpu = ImmutableSortedSet.copyOf(requestOptions.multiCpus);
     masterConfig = skyframeExecutor.createConfigurations(
         configurationFactory, buildOptions, directories, multiCpu, false);
-    analysisResult = buildView.update(loadingResult, masterConfig, viewOptions,
-        AnalysisTestUtil.TOP_LEVEL_ARTIFACT_CONTEXT, reporter, eventBus);
+    analysisResult =
+        buildView.update(
+            loadingResult,
+            masterConfig,
+            ImmutableList.<String>of(),
+            viewOptions,
+            AnalysisTestUtil.TOP_LEVEL_ARTIFACT_CONTEXT,
+            reporter,
+            eventBus);
   }
 
   protected void update(FlagBuilder config, String... labels) throws Exception {
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
index 46f4e4d..81f7dc4 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
@@ -1281,8 +1281,14 @@
       // TODO(bazel-team): What's supposed to happen in this case?
       return null;
     }
-    return view.update(loadingResult, masterConfig, viewOptions,
-        AnalysisTestUtil.TOP_LEVEL_ARTIFACT_CONTEXT, reporter, eventBus);
+    return view.update(
+        loadingResult,
+        masterConfig,
+        ImmutableList.<String>of(),
+        viewOptions,
+        AnalysisTestUtil.TOP_LEVEL_ARTIFACT_CONTEXT,
+        reporter,
+        eventBus);
   }
 
   protected static Predicate<Artifact> artifactNamed(final String name) {
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/TestAspects.java b/src/test/java/com/google/devtools/build/lib/analysis/util/TestAspects.java
index 5438b43..bf564fb 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/TestAspects.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/TestAspects.java
@@ -132,8 +132,9 @@
   public abstract static class BaseAspect implements ConfiguredAspectFactory {
     @Override
     public Aspect create(ConfiguredTarget base, RuleContext ruleContext) {
-      return new Aspect.Builder()
-          .addProvider(AspectInfo.class,
+      return new Aspect.Builder(getClass().getName())
+          .addProvider(
+              AspectInfo.class,
               new AspectInfo(collectAspectData("aspect " + ruleContext.getLabel(), ruleContext)))
           .build();
     }