Merge pull request #35 from brendandouglas/master

Import of bazel plugin using copybara
diff --git a/BUILD b/BUILD
index 8062413..1f6ee16 100644
--- a/BUILD
+++ b/BUILD
@@ -10,6 +10,7 @@
     tests = [
         "//base:integration_tests",
         "//base:unit_tests",
+        "//ijwb:unit_tests",
         "//java:integration_tests",
         "//java:unit_tests",
         "//plugin_dev:integration_tests",
@@ -20,6 +21,7 @@
 test_suite(
     name = "aswb_tests",
     tests = [
+        "//aswb:integration_tests",
         "//aswb:unit_tests",
         "//base:integration_tests",
         "//base:unit_tests",
diff --git a/WORKSPACE b/WORKSPACE
index 485d46f..c29d33a 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1,14 +1,14 @@
 workspace(name = "intellij_with_bazel")
 
-# The plugin api for IntelliJ 2016.1.3. This is required to build IJwB,
+# The plugin api for IntelliJ 2016.2.5. This is required to build IJwB,
 # and run integration tests.
 new_http_archive(
     name = "intellij_latest",
     build_file = "intellij_platform_sdk/BUILD.idea",
-    url = "https://download.jetbrains.com/idea/ideaIC-2016.2.4.tar.gz",
+    url = "https://download.jetbrains.com/idea/ideaIC-2016.2.5.tar.gz",
 )
 
-# The plugin api for CLion 2016.1.3. This is required to build CLwB,
+# The plugin api for CLion 2016.2.2. This is required to build CLwB,
 # and run integration tests.
 new_http_archive(
     name = "clion_latest",
diff --git a/aswb/BUILD b/aswb/BUILD
index 8c6fd4a..f3cfd00 100644
--- a/aswb/BUILD
+++ b/aswb/BUILD
@@ -61,8 +61,23 @@
     ],
 )
 
+java_library(
+    name = "integration_test_utils",
+    testonly = 1,
+    srcs = glob(["tests/utils/integration/**/*.java"]),
+    deps = [
+        "//base",
+        "//base:integration_test_utils",
+        "//base:unit_test_utils",
+        "//intellij_platform_sdk:plugin_api_for_tests",
+        "@jsr305_annotations//jar",
+        "@junit//jar",
+    ],
+)
+
 load(
     "//testing:test_defs.bzl",
+    "intellij_integration_test_suite",
     "intellij_unit_test_suite",
 )
 
@@ -84,6 +99,31 @@
     ],
 )
 
+intellij_integration_test_suite(
+    name = "integration_tests",
+    srcs = glob(["tests/integrationtests/**/*.java"]),
+    platform_prefix = "AndroidStudio",
+    required_plugins = "com.google.idea.bazel.aswb",
+    test_package_root = "com.google.idea.blaze.android",
+    runtime_deps = [
+        ":aswb_bazel",
+    ],
+    deps = [
+        ":aswb_lib",
+        ":integration_test_utils",
+        "//base",
+        "//base:integration_test_utils",
+        "//base:unit_test_utils",
+        "//common/experiments",
+        "//common/experiments:unit_test_utils",
+        "//intellij_platform_sdk:plugin_api_for_tests",
+        "//java",
+        "//proto_deps",
+        "@jsr305_annotations//jar",
+        "@junit//jar",
+    ],
+)
+
 intellij_plugin(
     name = "aswb_bazel",
     plugin_xml = ":stamped_plugin_xml",
diff --git a/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceUtils.java b/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceUtils.java
index 20f88d2..cd29787 100644
--- a/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceUtils.java
+++ b/aswb/src/com/google/idea/blaze/android/resources/actions/BlazeCreateResourceUtils.java
@@ -21,11 +21,11 @@
 import com.google.common.collect.Sets;
 import com.google.idea.blaze.android.sync.model.AndroidResourceModule;
 import com.google.idea.blaze.android.sync.model.BlazeAndroidSyncData;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.rulemaps.SourceToRuleMap;
 import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
+import com.google.idea.blaze.base.targetmaps.SourceToTargetMap;
 import com.intellij.ide.util.DirectoryUtil;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.project.Project;
@@ -61,12 +61,12 @@
     if (blazeProjectData != null) {
       BlazeAndroidSyncData syncData = blazeProjectData.syncState.get(BlazeAndroidSyncData.class);
       if (syncData != null) {
-        ImmutableCollection<RuleKey> rulesRelatedToContext = null;
+        ImmutableCollection<TargetKey> rulesRelatedToContext = null;
         File fileFromContext = null;
         if (contextFile != null) {
           fileFromContext = VfsUtilCore.virtualToIoFile(contextFile);
           rulesRelatedToContext =
-              SourceToRuleMap.getInstance(project).getRulesForSourceFile(fileFromContext);
+              SourceToTargetMap.getInstance(project).getRulesForSourceFile(fileFromContext);
           if (rulesRelatedToContext.isEmpty()) {
             rulesRelatedToContext = null;
           }
@@ -99,7 +99,7 @@
           allResDirs.addAll(transitiveResources);
 
           if (rulesRelatedToContext != null
-              && !rulesRelatedToContext.contains(androidResourceModule.ruleKey)) {
+              && !rulesRelatedToContext.contains(androidResourceModule.targetKey)) {
             continue;
           }
           resourceDirs.addAll(resources);
diff --git a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonState.java b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonState.java
index ed7a367..5bd0c77 100644
--- a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonState.java
+++ b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonState.java
@@ -18,6 +18,7 @@
 import static com.google.idea.blaze.android.cppapi.NdkSupport.NDK_SUPPORT;
 
 import com.android.tools.idea.run.ValidationError;
+import com.android.tools.idea.run.editor.AndroidDebugger;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.idea.blaze.android.cppapi.NdkSupport;
@@ -42,6 +43,8 @@
 
 /** A shared state class for run configurations targeting Blaze Android rules. */
 public class BlazeAndroidRunConfigurationCommonState implements RunConfigurationState {
+  private static final String DEPLOY_TARGET_STATES_TAG = "android-deploy-target-states";
+  private static final String DEBUGGER_STATES_TAG = "android-debugger-states";
   private static final String USER_FLAG_TAG = "blaze-user-flag";
   private static final String NATIVE_DEBUG_ATTR = "blaze-native-debug";
 
@@ -121,8 +124,23 @@
     userFlags.readExternal(element);
     setNativeDebuggingEnabled(Boolean.parseBoolean(element.getAttributeValue(NATIVE_DEBUG_ATTR)));
 
-    deployTargetManager.readExternal(element);
-    debuggerManager.readExternal(element);
+    Element deployTargetStatesElement = element.getChild(DEPLOY_TARGET_STATES_TAG);
+    if (deployTargetStatesElement != null) {
+      deployTargetManager.readExternal(deployTargetStatesElement);
+    } else {
+      // TODO Introduced in 1.12, remove in 1.14.
+      // This was for migrating the state to a child element.
+      deployTargetManager.readExternal(element);
+    }
+
+    Element debuggerStatesElement = element.getChild(DEBUGGER_STATES_TAG);
+    if (debuggerStatesElement != null) {
+      debuggerManager.readExternal(debuggerStatesElement);
+    } else {
+      // TODO Introduced in 1.12, remove in 1.14.
+      // This was for migrating the state to a child element.
+      debuggerManager.readExternal(element);
+    }
   }
 
   @Override
@@ -130,8 +148,30 @@
     userFlags.writeExternal(element);
     element.setAttribute(NATIVE_DEBUG_ATTR, Boolean.toString(nativeDebuggingEnabled));
 
-    deployTargetManager.writeExternal(element);
-    debuggerManager.writeExternal(element);
+    removeOldManagerState(element);
+
+    element.removeChildren(DEPLOY_TARGET_STATES_TAG);
+    Element deployTargetStatesElement = new Element(DEPLOY_TARGET_STATES_TAG);
+    deployTargetManager.writeExternal(deployTargetStatesElement);
+    element.addContent(deployTargetStatesElement);
+
+    element.removeChildren(DEBUGGER_STATES_TAG);
+    Element debuggerStatesElement = new Element(DEBUGGER_STATES_TAG);
+    debuggerManager.writeExternal(debuggerStatesElement);
+    element.addContent(debuggerStatesElement);
+  }
+
+  // TODO Introduced in 1.12, remove in 1.14. This was for migrating state
+  // and cleaning up mass amounts of duplicate state caused by never removing these elements before.
+  private void removeOldManagerState(Element element) {
+    // This is safe because we know only BlazeAndroidRunConfigurationDeployTargetManager
+    // directly wrote option elements (via DefaultJDOMExternalizer.writeExternal) to our root.
+    element.removeChildren("option");
+    // BlazeAndroidRunConfigurationDebuggerManager, meanwhile, nested its state in
+    // child elements named after the AndroidDebugger extension IDs.
+    for (AndroidDebugger<?> debugger : AndroidDebugger.EP_NAME.getExtensions()) {
+      element.removeChildren(debugger.getId());
+    }
   }
 
   @Override
diff --git a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationFactory.java b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationFactory.java
index d398ece..30955bf 100644
--- a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationFactory.java
+++ b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationFactory.java
@@ -15,8 +15,8 @@
  */
 package com.google.idea.blaze.android.run;
 
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.model.primitives.Label;
@@ -30,9 +30,9 @@
 /** Creates run configurations for android_binary and android_test. */
 public class BlazeAndroidRunConfigurationFactory extends BlazeRunConfigurationFactory {
   @Override
-  public boolean handlesTarget(Project project, BlazeProjectData blazeProjectData, Label target) {
-    RuleIdeInfo rule = blazeProjectData.ruleMap.get(RuleKey.forPlainTarget(target));
-    return rule != null && rule.kindIsOneOf(Kind.ANDROID_BINARY, Kind.ANDROID_TEST);
+  public boolean handlesTarget(Project project, BlazeProjectData blazeProjectData, Label label) {
+    TargetIdeInfo target = blazeProjectData.targetMap.get(TargetKey.forPlainTarget(label));
+    return target != null && target.kindIsOneOf(Kind.ANDROID_BINARY, Kind.ANDROID_TEST);
   }
 
   @Override
diff --git a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationValidationUtil.java b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationValidationUtil.java
index 2c0c26e..992f414 100644
--- a/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationValidationUtil.java
+++ b/aswb/src/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationValidationUtil.java
@@ -19,11 +19,11 @@
 import com.android.tools.idea.run.ValidationError;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.run.rulefinder.RuleFinder;
+import com.google.idea.blaze.base.run.targetfinder.TargetFinder;
 import com.google.idea.blaze.base.settings.Blaze;
 import com.intellij.execution.ExecutionException;
 import com.intellij.execution.configurations.RuntimeConfigurationError;
@@ -98,12 +98,12 @@
       errors.add(ValidationError.fatal("No target selected."));
       return errors;
     }
-    RuleIdeInfo rule = RuleFinder.getInstance().ruleForTarget(project, label);
-    if (rule == null) {
+    TargetIdeInfo target = TargetFinder.getInstance().targetForLabel(project, label);
+    if (target == null) {
       errors.add(
           ValidationError.fatal(
               String.format("No existing %s rule selected.", Blaze.buildSystemName(project))));
-    } else if (!rule.kindIsOneOf(kind)) {
+    } else if (!target.kindIsOneOf(kind)) {
       errors.add(
           ValidationError.fatal(
               String.format(
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java
index 412c341..b90cc35 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryNormalBuildRunContext.java
@@ -121,7 +121,7 @@
       BlazeAndroidRunConfigurationDebuggerManager debuggerManager)
       throws ExecutionException {
     return new BlazeAndroidLaunchTasksProvider(
-        project, this, applicationIdProvider, launchOptionsBuilder, isDebug, debuggerManager);
+        project, this, applicationIdProvider, launchOptionsBuilder, isDebug, true, debuggerManager);
   }
 
   @Nullable
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeAndroidBinaryInstantRunContext.java b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeAndroidBinaryInstantRunContext.java
index f365e5d..b35d0b2 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeAndroidBinaryInstantRunContext.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeAndroidBinaryInstantRunContext.java
@@ -125,7 +125,13 @@
       return new UpdateSessionTasksProvider(analyzer);
     }
     return new BlazeAndroidLaunchTasksProvider(
-        project, this, getApplicationIdProvider(), launchOptionsBuilder, isDebug, debuggerManager);
+        project,
+        this,
+        getApplicationIdProvider(),
+        launchOptionsBuilder,
+        isDebug,
+        true,
+        debuggerManager);
   }
 
   @Override
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeApkBuildStepInstantRun.java b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeApkBuildStepInstantRun.java
index 75821b1..2830533 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeApkBuildStepInstantRun.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/instantrun/BlazeApkBuildStepInstantRun.java
@@ -213,7 +213,7 @@
                     .stderr(
                         LineProcessingOutputStream.of(
                             new ExperimentalShowArtifactsLineProcessor(
-                                apkManifestFiles, "apk_manifest"),
+                                apkManifestFiles, fileName -> fileName.endsWith("apk_manifest")),
                             new IssueOutputLineProcessor(project, context, workspaceRoot)))
                     .build()
                     .run(new LoggedTimingScope(project, Action.BLAZE_BUILD));
diff --git a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java b/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java
index 205c95c..a25eba9 100644
--- a/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java
+++ b/aswb/src/com/google/idea/blaze/android/run/binary/mobileinstall/BlazeAndroidBinaryMobileInstallRunContext.java
@@ -120,7 +120,7 @@
       BlazeAndroidRunConfigurationDebuggerManager debuggerManager)
       throws ExecutionException {
     return new BlazeAndroidLaunchTasksProvider(
-        project, this, applicationIdProvider, launchOptionsBuilder, isDebug, debuggerManager);
+        project, this, applicationIdProvider, launchOptionsBuilder, isDebug, true, debuggerManager);
   }
 
   @Override
diff --git a/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeApkDeployInfoProtoHelper.java b/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeApkDeployInfoProtoHelper.java
index 477e1f2..241fd76 100644
--- a/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeApkDeployInfoProtoHelper.java
+++ b/aswb/src/com/google/idea/blaze/android/run/deployinfo/BlazeApkDeployInfoProtoHelper.java
@@ -46,7 +46,8 @@
   private final ImmutableList<String> buildFlags;
   private final List<File> deployInfoFiles = Lists.newArrayList();
   private final LineProcessingOutputStream.LineProcessor lineProcessor =
-      new ExperimentalShowArtifactsLineProcessor(deployInfoFiles, ".deployinfo.pb");
+      new ExperimentalShowArtifactsLineProcessor(
+          deployInfoFiles, fileName -> fileName.endsWith(".deployinfo.pb"));
 
   public BlazeApkDeployInfoProtoHelper(Project project, ImmutableList<String> buildFlags) {
     this.project = project;
diff --git a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java
index 102512a..7354b25 100644
--- a/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java
+++ b/aswb/src/com/google/idea/blaze/android/run/runner/BlazeAndroidLaunchTasksProvider.java
@@ -53,6 +53,7 @@
   private final ApplicationIdProvider applicationIdProvider;
   private final LaunchOptions.Builder launchOptionsBuilder;
   private final boolean isDebug;
+  private final boolean monitorRemoteProcess;
   private final BlazeAndroidRunConfigurationDebuggerManager debuggerManager;
 
   public BlazeAndroidLaunchTasksProvider(
@@ -61,12 +62,14 @@
       ApplicationIdProvider applicationIdProvider,
       LaunchOptions.Builder launchOptionsBuilder,
       boolean isDebug,
+      boolean monitorRemoteProcess,
       BlazeAndroidRunConfigurationDebuggerManager debuggerManager) {
     this.project = project;
     this.runContext = runContext;
     this.applicationIdProvider = applicationIdProvider;
     this.launchOptionsBuilder = launchOptionsBuilder;
     this.isDebug = isDebug;
+    this.monitorRemoteProcess = monitorRemoteProcess;
     this.debuggerManager = debuggerManager;
   }
 
@@ -180,6 +183,6 @@
 
   @Override
   public boolean monitorRemoteProcess() {
-    return true;
+    return monitorRemoteProcess;
   }
 }
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestClassRunConfigurationProducer.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestClassRunConfigurationProducer.java
index f48fd2c..64e8239 100644
--- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestClassRunConfigurationProducer.java
+++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestClassRunConfigurationProducer.java
@@ -17,7 +17,7 @@
 
 import com.android.tools.idea.run.testing.AndroidTestRunConfiguration;
 import com.google.common.base.Strings;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
 import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
@@ -71,14 +71,14 @@
     }
     sourceElement.set(testClass);
 
-    RuleIdeInfo rule = RunUtil.ruleForTestClass(context.getProject(), testClass, null);
-    if (rule == null) {
+    TargetIdeInfo target = RunUtil.targetForTestClass(context.getProject(), testClass, null);
+    if (target == null) {
       return false;
     }
-    if (!rule.kindIsOneOf(Kind.ANDROID_TEST)) {
+    if (!target.kindIsOneOf(Kind.ANDROID_TEST)) {
       return false;
     }
-    configuration.setTarget(rule.label);
+    configuration.setTarget(target.key.label);
     BlazeAndroidTestRunConfigurationState configState =
         configuration.getHandlerStateIfType(BlazeAndroidTestRunConfigurationState.class);
     if (configState == null) {
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestMethodRunConfigurationProducer.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestMethodRunConfigurationProducer.java
index 4334cd4..0c42ea9 100644
--- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestMethodRunConfigurationProducer.java
+++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestMethodRunConfigurationProducer.java
@@ -17,7 +17,7 @@
 
 import com.android.tools.idea.run.testing.AndroidTestRunConfiguration;
 import com.google.common.base.Strings;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
 import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
@@ -71,14 +71,14 @@
       return false;
     }
 
-    RuleIdeInfo rule = RunUtil.ruleForTestClass(context.getProject(), containingClass, null);
-    if (rule == null) {
+    TargetIdeInfo target = RunUtil.targetForTestClass(context.getProject(), containingClass, null);
+    if (target == null) {
       return false;
     }
-    if (!rule.kindIsOneOf(Kind.ANDROID_TEST)) {
+    if (!target.kindIsOneOf(Kind.ANDROID_TEST)) {
       return false;
     }
-    configuration.setTarget(rule.label);
+    configuration.setTarget(target.key.label);
     BlazeAndroidTestRunConfigurationState configState =
         configuration.getHandlerStateIfType(BlazeAndroidTestRunConfigurationState.class);
     if (configState == null) {
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java
index 7ab695c..ee1013b 100644
--- a/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java
+++ b/aswb/src/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunContext.java
@@ -126,7 +126,13 @@
       BlazeAndroidRunConfigurationDebuggerManager debuggerManager)
       throws ExecutionException {
     return new BlazeAndroidLaunchTasksProvider(
-        project, this, applicationIdProvider, launchOptionsBuilder, isDebug, debuggerManager);
+        project,
+        this,
+        applicationIdProvider,
+        launchOptionsBuilder,
+        isDebug,
+        false,
+        debuggerManager);
   }
 
   @Override
diff --git a/aswb/src/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTask.java b/aswb/src/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTask.java
index 9fe3377..7153880 100644
--- a/aswb/src/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTask.java
+++ b/aswb/src/com/google/idea/blaze/android/run/test/ConnectBlazeTestDebuggerTask.java
@@ -164,24 +164,27 @@
     // reference to the launch status and printers, and those should
     // be updated to point to the new process handlers,
     // otherwise test results will not be forwarded appropriately
+    ProcessHandler oldProcessHandler = launchStatus.getProcessHandler();
     launchStatus.setProcessHandler(debugProcessHandler);
     printer.setProcessHandler(debugProcessHandler);
 
-    // detach old process handler
-    RunContentDescriptor descriptor = currentLaunchInfo.env.getContentToReuse();
-    assert descriptor != null;
-
-    final ProcessHandler processHandler = descriptor.getProcessHandler();
-
-    // detach after the launch status has been updated to point to the new process handler
-    if (processHandler != null) {
-      processHandler.detachProcess();
-    }
+    // Detach old process handler after the launch status
+    // has been updated to point to the new process handler.
+    oldProcessHandler.detachProcess();
 
     AndroidDebugState debugState =
         new AndroidDebugState(
             project, debugProcessHandler, connection, currentLaunchInfo.consoleProvider);
 
+    RunContentDescriptor oldDescriptor;
+    AndroidSessionInfo oldSession = oldProcessHandler.getUserData(AndroidSessionInfo.KEY);
+    if (oldSession != null) {
+      oldDescriptor = oldSession.getDescriptor();
+    } else {
+      // This is the first time we are attaching the debugger; get it from the environment instead.
+      oldDescriptor = currentLaunchInfo.env.getContentToReuse();
+    }
+
     RunContentDescriptor debugDescriptor;
     try {
       // @formatter:off
@@ -189,7 +192,7 @@
           new ExecutionEnvironmentBuilder(currentLaunchInfo.env)
               .executor(currentLaunchInfo.executor)
               .runner(currentLaunchInfo.runner)
-              .contentToReuse(processHandler == null ? null : descriptor)
+              .contentToReuse(oldDescriptor)
               .build();
       debugDescriptor =
           DebuggerPanelsManager.getInstance(project)
@@ -206,11 +209,9 @@
     // re-run the collected text from the old process handler to the new
     // TODO: is there a race between messages received once the debugger has been connected,
     // and these messages that are printed out?
-    if (processHandler != null) {
-      final AndroidProcessText oldText = AndroidProcessText.get(processHandler);
-      if (oldText != null) {
-        oldText.printTo(debugProcessHandler);
-      }
+    final AndroidProcessText oldText = AndroidProcessText.get(oldProcessHandler);
+    if (oldText != null) {
+      oldText.printTo(debugProcessHandler);
     }
 
     RunProfile runProfile = currentLaunchInfo.env.getRunProfile();
diff --git a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidJavaSyncAugmenter.java b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidJavaSyncAugmenter.java
index ffc3edb..e6240bd 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidJavaSyncAugmenter.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidJavaSyncAugmenter.java
@@ -15,72 +15,49 @@
  */
 package com.google.idea.blaze.android.sync;
 
-import com.google.common.collect.ImmutableList;
 import com.google.idea.blaze.android.sync.importer.BlazeAndroidWorkspaceImporter;
-import com.google.idea.blaze.android.sync.model.BlazeAndroidSyncData;
-import com.google.idea.blaze.base.ideinfo.AndroidRuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo;
 import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.model.primitives.LanguageClass;
-import com.google.idea.blaze.base.projectview.section.Glob;
 import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
 import com.google.idea.blaze.java.sync.BlazeJavaSyncAugmenter;
 import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
-import com.google.idea.blaze.java.sync.model.BlazeLibrary;
 import java.util.Collection;
 
 /** Augments the java sync process with Android support. */
-public class BlazeAndroidJavaSyncAugmenter extends BlazeJavaSyncAugmenter.Adapter {
-
+public class BlazeAndroidJavaSyncAugmenter implements BlazeJavaSyncAugmenter {
   @Override
-  public boolean isActive(WorkspaceLanguageSettings workspaceLanguageSettings) {
-    return workspaceLanguageSettings.isLanguageActive(LanguageClass.ANDROID);
-  }
-
-  @Override
-  public void addJarsForSourceRule(
-      RuleIdeInfo rule, Collection<BlazeJarLibrary> jars, Collection<BlazeJarLibrary> genJars) {
-    AndroidRuleIdeInfo androidRuleIdeInfo = rule.androidRuleIdeInfo;
-    if (androidRuleIdeInfo == null) {
+  public void addJarsForSourceTarget(
+      WorkspaceLanguageSettings workspaceLanguageSettings,
+      TargetIdeInfo target,
+      Collection<BlazeJarLibrary> jars,
+      Collection<BlazeJarLibrary> genJars) {
+    if (!workspaceLanguageSettings.isLanguageActive(LanguageClass.ANDROID)) {
       return;
     }
-    LibraryArtifact idlJar = androidRuleIdeInfo.idlJar;
+    AndroidIdeInfo androidIdeInfo = target.androidIdeInfo;
+    if (androidIdeInfo == null) {
+      return;
+    }
+    LibraryArtifact idlJar = androidIdeInfo.idlJar;
     if (idlJar != null) {
-      genJars.add(new BlazeJarLibrary(idlJar, rule.key));
+      genJars.add(new BlazeJarLibrary(idlJar, target.key));
     }
 
-    if (BlazeAndroidWorkspaceImporter.shouldGenerateResources(androidRuleIdeInfo)
-        && !BlazeAndroidWorkspaceImporter.shouldGenerateResourceModule(androidRuleIdeInfo)) {
+    if (BlazeAndroidWorkspaceImporter.shouldGenerateResources(androidIdeInfo)
+        && !BlazeAndroidWorkspaceImporter.shouldGenerateResourceModule(androidIdeInfo)) {
       // Add blaze's output unless it's a top level rule.
       // In these cases the resource jar contains the entire
       // transitive closure of R classes. It's unlikely this is wanted to resolve in the IDE.
-      boolean discardResourceJar = rule.kindIsOneOf(Kind.ANDROID_BINARY, Kind.ANDROID_TEST);
+      boolean discardResourceJar = target.kindIsOneOf(Kind.ANDROID_BINARY, Kind.ANDROID_TEST);
       if (!discardResourceJar) {
-        LibraryArtifact resourceJar = androidRuleIdeInfo.resourceJar;
+        LibraryArtifact resourceJar = androidIdeInfo.resourceJar;
         if (resourceJar != null) {
-          jars.add(new BlazeJarLibrary(resourceJar, rule.key));
+          jars.add(new BlazeJarLibrary(resourceJar, target.key));
         }
       }
     }
   }
-
-  @Override
-  public void addLibraryFilter(Glob.GlobSet excludedLibraries) {
-    excludedLibraries.add(new Glob("*/android_blaze.jar")); // This is supplied via the SDK
-  }
-
-  @Override
-  public Collection<BlazeLibrary> getAdditionalLibraries(BlazeProjectData blazeProjectData) {
-    BlazeAndroidSyncData syncData = blazeProjectData.syncState.get(BlazeAndroidSyncData.class);
-    if (syncData == null) {
-      return ImmutableList.of();
-    }
-    ImmutableList.Builder<BlazeLibrary> libraries = ImmutableList.builder();
-    if (syncData.importResult.resourceLibrary != null) {
-      libraries.add(syncData.importResult.resourceLibrary);
-    }
-    return libraries.build();
-  }
 }
diff --git a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidLibrarySource.java b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidLibrarySource.java
new file mode 100644
index 0000000..bfff837
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidLibrarySource.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.android.sync;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.android.sync.model.BlazeAndroidSyncData;
+import com.google.idea.blaze.base.model.BlazeLibrary;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.projectview.section.Glob;
+import com.google.idea.blaze.base.projectview.section.Glob.GlobSet;
+import com.google.idea.blaze.base.sync.libraries.LibrarySource;
+import com.google.idea.blaze.java.sync.LibraryGlobFilter;
+import java.util.Collection;
+import java.util.function.Predicate;
+import javax.annotation.Nullable;
+
+class BlazeAndroidLibrarySource extends LibrarySource.Adapter {
+  private final BlazeProjectData blazeProjectData;
+
+  BlazeAndroidLibrarySource(BlazeProjectData blazeProjectData) {
+    this.blazeProjectData = blazeProjectData;
+  }
+
+  @Override
+  public Collection<BlazeLibrary> getLibraries() {
+    BlazeAndroidSyncData syncData = blazeProjectData.syncState.get(BlazeAndroidSyncData.class);
+    if (syncData == null) {
+      return ImmutableList.of();
+    }
+    ImmutableList.Builder<BlazeLibrary> libraries = ImmutableList.builder();
+    if (syncData.importResult.resourceLibrary != null) {
+      libraries.add(syncData.importResult.resourceLibrary);
+    }
+    return libraries.build();
+  }
+
+  @Nullable
+  @Override
+  public Predicate<BlazeLibrary> getLibraryFilter() {
+    // This is supplied via the SDK
+    GlobSet globSet = new GlobSet(ImmutableList.of(new Glob("*/android_blaze.jar")));
+    return new LibraryGlobFilter(globSet);
+  }
+}
diff --git a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncPlugin.java b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncPlugin.java
index a33fa08..52c21b2 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncPlugin.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/BlazeAndroidSyncPlugin.java
@@ -30,7 +30,7 @@
 import com.google.idea.blaze.android.sync.sdk.AndroidSdkFromProjectView;
 import com.google.idea.blaze.android.sync.sdk.SdkExperiment;
 import com.google.idea.blaze.android.sync.sdklegacy.AndroidSdkPlatformSyncer;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.SyncState;
 import com.google.idea.blaze.base.model.primitives.LanguageClass;
@@ -43,7 +43,10 @@
 import com.google.idea.blaze.base.scope.output.IssueOutput;
 import com.google.idea.blaze.base.scope.output.StatusOutput;
 import com.google.idea.blaze.base.scope.scopes.TimingScope;
+import com.google.idea.blaze.base.settings.Blaze;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
 import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.google.idea.blaze.base.sync.libraries.LibrarySource;
 import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
 import com.google.idea.blaze.base.sync.workspace.BlazeRoots;
@@ -73,6 +76,11 @@
 /** ASwB sync plugin. */
 public class BlazeAndroidSyncPlugin extends BlazeSyncPlugin.Adapter {
 
+  @Override
+  public ImmutableList<WorkspaceType> getSupportedWorkspaceTypes() {
+    return ImmutableList.of(WorkspaceType.ANDROID, WorkspaceType.ANDROID_NDK);
+  }
+
   @Nullable
   @Override
   public WorkspaceType getDefaultWorkspaceType() {
@@ -129,7 +137,7 @@
       @Nullable WorkingSet workingSet,
       WorkspacePathResolver workspacePathResolver,
       ArtifactLocationDecoder artifactLocationDecoder,
-      RuleMap ruleMap,
+      TargetMap targetMap,
       SyncState.Builder syncStateBuilder,
       @Nullable SyncState previousSyncState) {
     if (!isAndroidWorkspace(workspaceLanguageSettings)) {
@@ -144,7 +152,8 @@
     }
 
     BlazeAndroidWorkspaceImporter workspaceImporter =
-        new BlazeAndroidWorkspaceImporter(project, context, workspaceRoot, projectViewSet, ruleMap);
+        new BlazeAndroidWorkspaceImporter(
+            project, context, workspaceRoot, projectViewSet, targetMap);
     BlazeAndroidImportResult importResult =
         Scope.push(
             context,
@@ -181,8 +190,12 @@
       return;
     }
 
+    LanguageLevel defaultLanguageLevel =
+        Blaze.getBuildSystem(project) == BuildSystem.Blaze
+            ? LanguageLevel.JDK_1_8
+            : LanguageLevel.JDK_1_7;
     LanguageLevel javaLanguageLevel =
-        JavaLanguageLevelSection.getLanguageLevel(projectViewSet, LanguageLevel.JDK_1_7);
+        JavaLanguageLevelSection.getLanguageLevel(projectViewSet, defaultLanguageLevel);
     setProjectSdkAndLanguageLevel(project, sdk, javaLanguageLevel);
   }
 
@@ -288,6 +301,15 @@
     return ImmutableList.of(AndroidSdkPlatformSection.PARSER);
   }
 
+  @Nullable
+  @Override
+  public LibrarySource getLibrarySource(BlazeProjectData blazeProjectData) {
+    if (!isAndroidWorkspace(blazeProjectData.workspaceLanguageSettings)) {
+      return null;
+    }
+    return new BlazeAndroidLibrarySource(blazeProjectData);
+  }
+
   private static boolean isAndroidWorkspace(WorkspaceLanguageSettings workspaceLanguageSettings) {
     return workspaceLanguageSettings.isWorkspaceType(
         WorkspaceType.ANDROID, WorkspaceType.ANDROID_NDK);
diff --git a/aswb/src/com/google/idea/blaze/android/sync/importer/BlazeAndroidWorkspaceImporter.java b/aswb/src/com/google/idea/blaze/android/sync/importer/BlazeAndroidWorkspaceImporter.java
index a519f28..7a3651b 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/importer/BlazeAndroidWorkspaceImporter.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/importer/BlazeAndroidWorkspaceImporter.java
@@ -25,18 +25,18 @@
 import com.google.idea.blaze.android.sync.model.AndroidResourceModule;
 import com.google.idea.blaze.android.sync.model.BlazeAndroidImportResult;
 import com.google.idea.blaze.android.sync.model.BlazeResourceLibrary;
-import com.google.idea.blaze.base.ideinfo.AndroidRuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import com.google.idea.blaze.base.model.primitives.LanguageClass;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
 import com.google.idea.blaze.base.scope.BlazeContext;
 import com.google.idea.blaze.base.scope.output.IssueOutput;
 import com.google.idea.blaze.base.scope.output.PerformanceWarning;
-import com.google.idea.blaze.base.sync.projectview.ProjectViewRuleImportFilter;
+import com.google.idea.blaze.base.sync.projectview.ProjectViewTargetImportFilter;
 import com.intellij.openapi.project.Project;
 import java.util.Collection;
 import java.util.Collections;
@@ -51,37 +51,37 @@
 public final class BlazeAndroidWorkspaceImporter {
 
   private final BlazeContext context;
-  private final RuleMap ruleMap;
-  private final ProjectViewRuleImportFilter importFilter;
+  private final TargetMap targetMap;
+  private final ProjectViewTargetImportFilter importFilter;
 
   public BlazeAndroidWorkspaceImporter(
       Project project,
       BlazeContext context,
       WorkspaceRoot workspaceRoot,
       ProjectViewSet projectViewSet,
-      RuleMap ruleMap) {
+      TargetMap targetMap) {
     this.context = context;
-    this.ruleMap = ruleMap;
-    this.importFilter = new ProjectViewRuleImportFilter(project, workspaceRoot, projectViewSet);
+    this.targetMap = targetMap;
+    this.importFilter = new ProjectViewTargetImportFilter(project, workspaceRoot, projectViewSet);
   }
 
   public BlazeAndroidImportResult importWorkspace() {
-    List<RuleIdeInfo> rules =
-        ruleMap
-            .rules()
+    List<TargetIdeInfo> targets =
+        targetMap
+            .targets()
             .stream()
-            .filter(rule -> rule.kind.getLanguageClass() == LanguageClass.ANDROID)
-            .filter(rule -> rule.androidRuleIdeInfo != null)
-            .filter(importFilter::isSourceRule)
-            .filter(rule -> !importFilter.excludeTarget(rule))
+            .filter(target -> target.kind.getLanguageClass() == LanguageClass.ANDROID)
+            .filter(target -> target.androidIdeInfo != null)
+            .filter(importFilter::isSourceTarget)
+            .filter(target -> !importFilter.excludeTarget(target))
             .collect(Collectors.toList());
 
-    TransitiveResourceMap transitiveResourceMap = new TransitiveResourceMap(ruleMap);
+    TransitiveResourceMap transitiveResourceMap = new TransitiveResourceMap(targetMap);
 
     WorkspaceBuilder workspaceBuilder = new WorkspaceBuilder();
 
-    for (RuleIdeInfo rule : rules) {
-      addSourceRule(workspaceBuilder, transitiveResourceMap, rule);
+    for (TargetIdeInfo target : targets) {
+      addSourceTarget(workspaceBuilder, transitiveResourceMap, target);
     }
 
     warnAboutGeneratedResources(workspaceBuilder.generatedResourceLocations);
@@ -93,18 +93,17 @@
     return new BlazeAndroidImportResult(androidResourceModules, resourceLibrary);
   }
 
-  private void addSourceRule(
+  private void addSourceTarget(
       WorkspaceBuilder workspaceBuilder,
       TransitiveResourceMap transitiveResourceMap,
-      RuleIdeInfo rule) {
-    AndroidRuleIdeInfo androidRuleIdeInfo = rule.androidRuleIdeInfo;
-    assert androidRuleIdeInfo != null;
-    if (shouldGenerateResources(androidRuleIdeInfo)
-        && shouldGenerateResourceModule(androidRuleIdeInfo)) {
-      AndroidResourceModule.Builder builder = new AndroidResourceModule.Builder(rule.key);
+      TargetIdeInfo target) {
+    AndroidIdeInfo androidIdeInfo = target.androidIdeInfo;
+    assert androidIdeInfo != null;
+    if (shouldGenerateResources(androidIdeInfo) && shouldGenerateResourceModule(androidIdeInfo)) {
+      AndroidResourceModule.Builder builder = new AndroidResourceModule.Builder(target.key);
       workspaceBuilder.androidResourceModules.add(builder);
 
-      for (ArtifactLocation artifactLocation : androidRuleIdeInfo.resources) {
+      for (ArtifactLocation artifactLocation : androidIdeInfo.resources) {
         if (artifactLocation.isSource()) {
           builder.addResource(artifactLocation);
         } else {
@@ -113,7 +112,7 @@
       }
 
       TransitiveResourceMap.TransitiveResourceInfo transitiveResourceInfo =
-          transitiveResourceMap.get(rule.key);
+          transitiveResourceMap.get(target.key);
       for (ArtifactLocation artifactLocation : transitiveResourceInfo.transitiveResources) {
         if (artifactLocation.isSource()) {
           builder.addTransitiveResource(artifactLocation);
@@ -121,25 +120,25 @@
           workspaceBuilder.generatedResourceLocations.add(artifactLocation);
         }
       }
-      for (RuleKey resourceDependency : transitiveResourceInfo.transitiveResourceRules) {
-        if (!resourceDependency.equals(rule.key)) {
+      for (TargetKey resourceDependency : transitiveResourceInfo.transitiveResourceTargets) {
+        if (!resourceDependency.equals(target.key)) {
           builder.addTransitiveResourceDependency(resourceDependency);
         }
       }
     }
   }
 
-  public static boolean shouldGenerateResources(AndroidRuleIdeInfo androidRuleIdeInfo) {
+  public static boolean shouldGenerateResources(AndroidIdeInfo androidIdeInfo) {
     // Generate an android resource module if this rule defines resources
     // We don't want to generate one if this depends on a legacy resource rule through :resources
     // In this case, the resource information is redundantly forwarded to this class for
     // backwards compatibility, but the android_resource rule itself is already generating
     // the android resource module
-    return androidRuleIdeInfo.generateResourceClass && androidRuleIdeInfo.legacyResources == null;
+    return androidIdeInfo.generateResourceClass && androidIdeInfo.legacyResources == null;
   }
 
-  public static boolean shouldGenerateResourceModule(AndroidRuleIdeInfo androidRuleIdeInfo) {
-    return androidRuleIdeInfo.resources.stream().anyMatch(ArtifactLocation::isSource);
+  public static boolean shouldGenerateResourceModule(AndroidIdeInfo androidIdeInfo) {
+    return androidIdeInfo.resources.stream().anyMatch(ArtifactLocation::isSource);
   }
 
   private void warnAboutGeneratedResources(Set<ArtifactLocation> generatedResourceLocations) {
@@ -188,11 +187,10 @@
     Multimap<String, AndroidResourceModule> javaPackageToResourceModule =
         ArrayListMultimap.create();
     for (AndroidResourceModule androidResourceModule : androidResourceModules) {
-      RuleIdeInfo rule = ruleMap.get(androidResourceModule.ruleKey);
-      AndroidRuleIdeInfo androidRuleIdeInfo = rule.androidRuleIdeInfo;
-      assert androidRuleIdeInfo != null;
-      javaPackageToResourceModule.put(
-          androidRuleIdeInfo.resourceJavaPackage, androidResourceModule);
+      TargetIdeInfo target = targetMap.get(androidResourceModule.targetKey);
+      AndroidIdeInfo androidIdeInfo = target.androidIdeInfo;
+      assert androidIdeInfo != null;
+      javaPackageToResourceModule.put(androidIdeInfo.resourceJavaPackage, androidResourceModule);
     }
 
     List<AndroidResourceModule> result = Lists.newArrayList();
@@ -210,7 +208,7 @@
             .append(".R: ");
         messageBuilder.append('\n');
         for (AndroidResourceModule androidResourceModule : androidResourceModulesWithJavaPackage) {
-          messageBuilder.append("  ").append(androidResourceModule.ruleKey).append('\n');
+          messageBuilder.append("  ").append(androidResourceModule.targetKey).append('\n');
         }
         String message = messageBuilder.toString();
         context.output(new PerformanceWarning(message));
@@ -220,7 +218,7 @@
       }
     }
 
-    Collections.sort(result, (lhs, rhs) -> RuleKey.COMPARATOR.compare(lhs.ruleKey, rhs.ruleKey));
+    Collections.sort(result, (lhs, rhs) -> lhs.targetKey.compareTo(rhs.targetKey));
     return ImmutableList.copyOf(result);
   }
 
@@ -236,8 +234,8 @@
                         lhs.transitiveResources.size(),
                         rhs.transitiveResources.size()) // Most transitive resources wins
                     .compare(
-                        rhs.ruleKey.toString().length(),
-                        lhs.ruleKey
+                        rhs.targetKey.toString().length(),
+                        lhs.targetKey
                             .toString()
                             .length()) // Shortest label wins - note lhs, rhs are flipped
                     .result())
diff --git a/aswb/src/com/google/idea/blaze/android/sync/importer/aggregators/RuleIdeInfoTransitiveAggregator.java b/aswb/src/com/google/idea/blaze/android/sync/importer/aggregators/RuleIdeInfoTransitiveAggregator.java
deleted file mode 100644
index 39a7ab4..0000000
--- a/aswb/src/com/google/idea/blaze/android/sync/importer/aggregators/RuleIdeInfoTransitiveAggregator.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2016 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.idea.blaze.android.sync.importer.aggregators;
-
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
-import com.google.idea.blaze.base.model.primitives.Label;
-
-/** Transitive aggregator for RuleIdeInfo. */
-public abstract class RuleIdeInfoTransitiveAggregator<T> extends TransitiveAggregator<T> {
-  protected RuleIdeInfoTransitiveAggregator(RuleMap ruleMap) {
-    super(ruleMap);
-  }
-
-  @Override
-  protected Iterable<Label> getDependencies(RuleIdeInfo ruleIdeInfo) {
-    return ruleIdeInfo.dependencies;
-  }
-}
diff --git a/aswb/src/com/google/idea/blaze/android/sync/importer/aggregators/TargetIdeInfoTransitiveAggregator.java b/aswb/src/com/google/idea/blaze/android/sync/importer/aggregators/TargetIdeInfoTransitiveAggregator.java
new file mode 100644
index 0000000..67fefa8
--- /dev/null
+++ b/aswb/src/com/google/idea/blaze/android/sync/importer/aggregators/TargetIdeInfoTransitiveAggregator.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.android.sync.importer.aggregators;
+
+import static java.util.stream.Collectors.toList;
+
+import com.google.idea.blaze.base.ideinfo.Dependency.DependencyType;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
+
+/** Transitive aggregator for TargetIdeInfo. */
+public abstract class TargetIdeInfoTransitiveAggregator<T> extends TransitiveAggregator<T> {
+  protected TargetIdeInfoTransitiveAggregator(TargetMap targetMap) {
+    super(targetMap);
+  }
+
+  @Override
+  protected Iterable<TargetKey> getDependencies(TargetIdeInfo target) {
+    return target
+        .dependencies
+        .stream()
+        .filter(dep -> dep.dependencyType == DependencyType.COMPILE_TIME)
+        .map(dep -> dep.targetKey)
+        .collect(toList());
+  }
+}
diff --git a/aswb/src/com/google/idea/blaze/android/sync/importer/aggregators/TransitiveAggregator.java b/aswb/src/com/google/idea/blaze/android/sync/importer/aggregators/TransitiveAggregator.java
index d2145f3..f3e71ab 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/importer/aggregators/TransitiveAggregator.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/importer/aggregators/TransitiveAggregator.java
@@ -16,58 +16,57 @@
 package com.google.idea.blaze.android.sync.importer.aggregators;
 
 import com.google.common.collect.Maps;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
-import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import java.util.Map;
 import org.jetbrains.annotations.Nullable;
 
-/** Peforms a transitive reduction on the rule */
+/** Peforms a transitive reduction on the targets */
 public abstract class TransitiveAggregator<T> {
-  private Map<RuleKey, T> ruleKeyToResult;
+  private Map<TargetKey, T> targetKeyToResult;
 
-  protected TransitiveAggregator(RuleMap ruleMap) {
-    this.ruleKeyToResult = Maps.newHashMap();
-    for (RuleIdeInfo rule : ruleMap.rules()) {
-      aggregate(rule.key, ruleMap);
+  protected TransitiveAggregator(TargetMap targetMap) {
+    this.targetKeyToResult = Maps.newHashMap();
+    for (TargetIdeInfo rule : targetMap.targets()) {
+      aggregate(rule.key, targetMap);
     }
   }
 
-  protected T getOrDefault(RuleKey ruleKey, T defaultValue) {
-    T result = ruleKeyToResult.get(ruleKey);
+  protected T getOrDefault(TargetKey targetKey, T defaultValue) {
+    T result = targetKeyToResult.get(targetKey);
     return result != null ? result : defaultValue;
   }
 
   @Nullable
-  private T aggregate(RuleKey ruleKey, RuleMap ruleMap) {
-    T result = ruleKeyToResult.get(ruleKey);
+  private T aggregate(TargetKey targetKey, TargetMap targetMap) {
+    T result = targetKeyToResult.get(targetKey);
     if (result != null) {
       return result;
     }
 
-    RuleIdeInfo rule = ruleMap.get(ruleKey);
-    if (rule == null) {
+    TargetIdeInfo target = targetMap.get(targetKey);
+    if (target == null) {
       return null;
     }
 
-    result = createForRule(rule);
+    result = createForTarget(target);
 
-    for (Label depLabel : getDependencies(rule)) {
-      T depResult = aggregate(RuleKey.forDependency(rule, depLabel), ruleMap);
+    for (TargetKey dep : getDependencies(target)) {
+      T depResult = aggregate(dep, targetMap);
       if (depResult != null) {
         result = reduce(result, depResult);
       }
     }
 
-    ruleKeyToResult.put(ruleKey, result);
+    targetKeyToResult.put(targetKey, result);
     return result;
   }
 
-  protected abstract Iterable<Label> getDependencies(RuleIdeInfo rule);
+  protected abstract Iterable<TargetKey> getDependencies(TargetIdeInfo target);
 
-  /** Creates the initial value for a given rule. */
-  protected abstract T createForRule(RuleIdeInfo rule);
+  /** Creates the initial value for a given target. */
+  protected abstract T createForTarget(TargetIdeInfo target);
 
   /** Reduces two values, sum + new value. May mutate value in place. */
   protected abstract T reduce(T value, T dependencyValue);
diff --git a/aswb/src/com/google/idea/blaze/android/sync/importer/aggregators/TransitiveResourceMap.java b/aswb/src/com/google/idea/blaze/android/sync/importer/aggregators/TransitiveResourceMap.java
index 366e9fd..5fba0d3 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/importer/aggregators/TransitiveResourceMap.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/importer/aggregators/TransitiveResourceMap.java
@@ -17,56 +17,56 @@
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
-import com.google.idea.blaze.base.ideinfo.AndroidRuleIdeInfo;
+import com.google.idea.blaze.android.sync.importer.aggregators.TransitiveResourceMap.TransitiveResourceInfo;
+import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
-import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import java.util.List;
 import java.util.Set;
 
 /** Computes transitive resources. */
 public class TransitiveResourceMap
-    extends RuleIdeInfoTransitiveAggregator<TransitiveResourceMap.TransitiveResourceInfo> {
+    extends TargetIdeInfoTransitiveAggregator<TransitiveResourceInfo> {
   /** The transitive info computed per-rule */
   public static class TransitiveResourceInfo {
     public static final TransitiveResourceInfo NO_RESOURCES = new TransitiveResourceInfo();
     public final Set<ArtifactLocation> transitiveResources = Sets.newHashSet();
-    public final Set<RuleKey> transitiveResourceRules = Sets.newHashSet();
+    public final Set<TargetKey> transitiveResourceTargets = Sets.newHashSet();
   }
 
-  public TransitiveResourceMap(RuleMap ruleMap) {
-    super(ruleMap);
+  public TransitiveResourceMap(TargetMap targetMap) {
+    super(targetMap);
   }
 
   @Override
-  protected Iterable<Label> getDependencies(RuleIdeInfo ruleIdeInfo) {
-    AndroidRuleIdeInfo androidRuleIdeInfo = ruleIdeInfo.androidRuleIdeInfo;
-    if (androidRuleIdeInfo != null && androidRuleIdeInfo.legacyResources != null) {
-      List<Label> result = Lists.newArrayList(super.getDependencies(ruleIdeInfo));
-      result.add(androidRuleIdeInfo.legacyResources);
+  protected Iterable<TargetKey> getDependencies(TargetIdeInfo target) {
+    AndroidIdeInfo androidIdeInfo = target.androidIdeInfo;
+    if (androidIdeInfo != null && androidIdeInfo.legacyResources != null) {
+      List<TargetKey> result = Lists.newArrayList(super.getDependencies(target));
+      result.add(TargetKey.forPlainTarget(androidIdeInfo.legacyResources));
       return result;
     }
-    return super.getDependencies(ruleIdeInfo);
+    return super.getDependencies(target);
   }
 
-  public TransitiveResourceInfo get(RuleKey ruleKey) {
-    return getOrDefault(ruleKey, TransitiveResourceInfo.NO_RESOURCES);
+  public TransitiveResourceInfo get(TargetKey targetKey) {
+    return getOrDefault(targetKey, TransitiveResourceInfo.NO_RESOURCES);
   }
 
   @Override
-  protected TransitiveResourceInfo createForRule(RuleIdeInfo ruleIdeInfo) {
+  protected TransitiveResourceInfo createForTarget(TargetIdeInfo target) {
     TransitiveResourceInfo result = new TransitiveResourceInfo();
-    AndroidRuleIdeInfo androidRuleIdeInfo = ruleIdeInfo.androidRuleIdeInfo;
-    if (androidRuleIdeInfo == null) {
+    AndroidIdeInfo androidIdeInfo = target.androidIdeInfo;
+    if (androidIdeInfo == null) {
       return result;
     }
-    if (androidRuleIdeInfo.legacyResources != null) {
+    if (androidIdeInfo.legacyResources != null) {
       return result;
     }
-    result.transitiveResources.addAll(androidRuleIdeInfo.resources);
-    result.transitiveResourceRules.add(ruleIdeInfo.key);
+    result.transitiveResources.addAll(androidIdeInfo.resources);
+    result.transitiveResourceTargets.add(target.key);
     return result;
   }
 
@@ -74,7 +74,7 @@
   protected TransitiveResourceInfo reduce(
       TransitiveResourceInfo value, TransitiveResourceInfo dependencyValue) {
     value.transitiveResources.addAll(dependencyValue.transitiveResources);
-    value.transitiveResourceRules.addAll(dependencyValue.transitiveResourceRules);
+    value.transitiveResourceTargets.addAll(dependencyValue.transitiveResourceTargets);
     return value;
   }
 }
diff --git a/aswb/src/com/google/idea/blaze/android/sync/model/AndroidResourceModule.java b/aswb/src/com/google/idea/blaze/android/sync/model/AndroidResourceModule.java
index d8f23d6..73d76f5 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/model/AndroidResourceModule.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/model/AndroidResourceModule.java
@@ -20,7 +20,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.model.primitives.Label;
 import java.io.Serializable;
 import java.util.List;
@@ -37,17 +37,17 @@
 public final class AndroidResourceModule implements Serializable {
   private static final long serialVersionUID = 8L;
 
-  public final RuleKey ruleKey;
+  public final TargetKey targetKey;
   public final ImmutableCollection<ArtifactLocation> resources;
   public final ImmutableCollection<ArtifactLocation> transitiveResources;
-  public final ImmutableCollection<RuleKey> transitiveResourceDependencies;
+  public final ImmutableCollection<TargetKey> transitiveResourceDependencies;
 
   public AndroidResourceModule(
-      RuleKey ruleKey,
+      TargetKey targetKey,
       ImmutableCollection<ArtifactLocation> resources,
       ImmutableCollection<ArtifactLocation> transitiveResources,
-      ImmutableCollection<RuleKey> transitiveResourceDependencies) {
-    this.ruleKey = ruleKey;
+      ImmutableCollection<TargetKey> transitiveResourceDependencies) {
+    this.targetKey = targetKey;
     this.resources = resources;
     this.transitiveResources = transitiveResources;
     this.transitiveResourceDependencies = transitiveResourceDependencies;
@@ -57,7 +57,7 @@
   public boolean equals(Object o) {
     if (o instanceof AndroidResourceModule) {
       AndroidResourceModule that = (AndroidResourceModule) o;
-      return Objects.equal(this.ruleKey, that.ruleKey)
+      return Objects.equal(this.targetKey, that.targetKey)
           && Objects.equal(this.resources, that.resources)
           && Objects.equal(this.transitiveResources, that.transitiveResources)
           && Objects.equal(
@@ -69,7 +69,7 @@
   @Override
   public int hashCode() {
     return Objects.hashCode(
-        this.ruleKey,
+        this.targetKey,
         this.resources,
         this.transitiveResources,
         this.transitiveResourceDependencies);
@@ -80,7 +80,7 @@
     return "AndroidResourceModule{"
         + "\n"
         + "  rule: "
-        + ruleKey
+        + targetKey
         + "\n"
         + "  resources: "
         + resources
@@ -94,8 +94,8 @@
         + '}';
   }
 
-  public static Builder builder(RuleKey ruleKey) {
-    return new Builder(ruleKey);
+  public static Builder builder(TargetKey targetKey) {
+    return new Builder(targetKey);
   }
 
   public boolean isEmpty() {
@@ -104,13 +104,13 @@
 
   /** Builder for the resource module */
   public static class Builder {
-    private final RuleKey ruleKey;
+    private final TargetKey targetKey;
     private final Set<ArtifactLocation> resources = Sets.newHashSet();
     private final Set<ArtifactLocation> transitiveResources = Sets.newHashSet();
-    private Set<RuleKey> transitiveResourceDependencies = Sets.newHashSet();
+    private Set<TargetKey> transitiveResourceDependencies = Sets.newHashSet();
 
-    public Builder(RuleKey ruleKey) {
-      this.ruleKey = ruleKey;
+    public Builder(TargetKey targetKey) {
+      this.targetKey = targetKey;
     }
 
     public Builder addResource(ArtifactLocation resource) {
@@ -134,13 +134,13 @@
       return this;
     }
 
-    public Builder addTransitiveResourceDependency(RuleKey dependency) {
+    public Builder addTransitiveResourceDependency(TargetKey dependency) {
       this.transitiveResourceDependencies.add(dependency);
       return this;
     }
 
     public Builder addTransitiveResourceDependency(Label dependency) {
-      this.transitiveResourceDependencies.add(RuleKey.forPlainTarget(dependency));
+      this.transitiveResourceDependencies.add(TargetKey.forPlainTarget(dependency));
       return this;
     }
 
@@ -151,17 +151,9 @@
     @NotNull
     public AndroidResourceModule build() {
       return new AndroidResourceModule(
-          ruleKey,
-          ImmutableList.copyOf(
-              resources
-                  .stream()
-                  .sorted()
-                  .collect(Collectors.toList())),
-          ImmutableList.copyOf(
-              transitiveResources
-                  .stream()
-                  .sorted()
-                  .collect(Collectors.toList())),
+          targetKey,
+          ImmutableList.copyOf(resources.stream().sorted().collect(Collectors.toList())),
+          ImmutableList.copyOf(transitiveResources.stream().sorted().collect(Collectors.toList())),
           ImmutableList.copyOf(
               transitiveResourceDependencies.stream().sorted().collect(Collectors.toList())));
     }
diff --git a/aswb/src/com/google/idea/blaze/android/sync/model/BlazeResourceLibrary.java b/aswb/src/com/google/idea/blaze/android/sync/model/BlazeResourceLibrary.java
index 9489a63..9ae9a3b 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/model/BlazeResourceLibrary.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/model/BlazeResourceLibrary.java
@@ -18,9 +18,9 @@
 import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableList;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
+import com.google.idea.blaze.base.model.BlazeLibrary;
+import com.google.idea.blaze.base.model.LibraryKey;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
-import com.google.idea.blaze.java.sync.model.BlazeLibrary;
-import com.google.idea.blaze.java.sync.model.LibraryKey;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.roots.OrderRootType;
 import com.intellij.openapi.roots.libraries.Library;
diff --git a/aswb/src/com/google/idea/blaze/android/sync/projectstructure/BlazeAndroidProjectStructureSyncer.java b/aswb/src/com/google/idea/blaze/android/sync/projectstructure/BlazeAndroidProjectStructureSyncer.java
index 7442635..76d4767 100644
--- a/aswb/src/com/google/idea/blaze/android/sync/projectstructure/BlazeAndroidProjectStructureSyncer.java
+++ b/aswb/src/com/google/idea/blaze/android/sync/projectstructure/BlazeAndroidProjectStructureSyncer.java
@@ -27,10 +27,10 @@
 import com.google.idea.blaze.android.sync.model.idea.BlazeAndroidModel;
 import com.google.idea.blaze.android.sync.model.idea.SourceProviderImpl;
 import com.google.idea.blaze.android.sync.sdk.SdkUtil;
-import com.google.idea.blaze.base.ideinfo.AndroidRuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.model.primitives.Label;
@@ -89,37 +89,38 @@
 
         // Create android resource modules
         // Because we're setting up dependencies, the modules have to exist before we configure them
-        Map<RuleKey, AndroidResourceModule> ruleToAndroidResourceModule = Maps.newHashMap();
+        Map<TargetKey, AndroidResourceModule> targetToAndroidResourceModule = Maps.newHashMap();
         for (AndroidResourceModule androidResourceModule :
             syncData.importResult.androidResourceModules) {
-          ruleToAndroidResourceModule.put(androidResourceModule.ruleKey, androidResourceModule);
-          String moduleName = moduleNameForAndroidModule(androidResourceModule.ruleKey);
+          targetToAndroidResourceModule.put(androidResourceModule.targetKey, androidResourceModule);
+          String moduleName = moduleNameForAndroidModule(androidResourceModule.targetKey);
           moduleEditor.createModule(moduleName, StdModuleTypes.JAVA);
         }
 
         // Configure android resource modules
-        for (AndroidResourceModule androidResourceModule : ruleToAndroidResourceModule.values()) {
-          RuleIdeInfo rule = blazeProjectData.ruleMap.get(androidResourceModule.ruleKey);
-          AndroidRuleIdeInfo androidRuleIdeInfo = rule.androidRuleIdeInfo;
-          assert androidRuleIdeInfo != null;
+        for (AndroidResourceModule androidResourceModule : targetToAndroidResourceModule.values()) {
+          TargetIdeInfo target = blazeProjectData.targetMap.get(androidResourceModule.targetKey);
+          AndroidIdeInfo androidIdeInfo = target.androidIdeInfo;
+          assert androidIdeInfo != null;
 
-          String moduleName = moduleNameForAndroidModule(rule.key);
+          String moduleName = moduleNameForAndroidModule(target.key);
           Module module = moduleEditor.findModule(moduleName);
           assert module != null;
           ModifiableRootModel modifiableRootModel = moduleEditor.editModule(module);
 
-          updateAndroidRuleModule(
+          updateAndroidTargetModule(
               project,
               workspaceRoot,
               blazeProjectData.artifactLocationDecoder,
               androidSdkPlatform,
-              rule,
+              target,
               module,
               modifiableRootModel,
               androidResourceModule);
 
-          for (RuleKey resourceDependency : androidResourceModule.transitiveResourceDependencies) {
-            if (!ruleToAndroidResourceModule.containsKey(resourceDependency)) {
+          for (TargetKey resourceDependency :
+              androidResourceModule.transitiveResourceDependencies) {
+            if (!targetToAndroidResourceModule.containsKey(resourceDependency)) {
               continue;
             }
             String dependencyModuleName = moduleNameForAndroidModule(resourceDependency);
@@ -130,7 +131,7 @@
             modifiableRootModel.addModuleOrderEntry(dependency);
             ++totalOrderEntries;
           }
-          rClassBuilder.addRClass(androidRuleIdeInfo.resourceJavaPackage, module);
+          rClassBuilder.addRClass(androidIdeInfo.resourceJavaPackage, module);
           // Add a dependency from the workspace to the resource module
           workspaceModifiableModel.addModuleOrderEntry(module);
         }
@@ -160,29 +161,29 @@
 
         int totalRunConfigurationModules = 0;
         for (Label label : runConfigurationModuleTargets) {
-          RuleKey ruleKey = RuleKey.forPlainTarget(label);
+          TargetKey targetKey = TargetKey.forPlainTarget(label);
           // If it's a resource module, it will already have been created
-          if (ruleToAndroidResourceModule.containsKey(ruleKey)) {
+          if (targetToAndroidResourceModule.containsKey(targetKey)) {
             continue;
           }
           // Ensure the label is a supported android rule that exists
-          RuleIdeInfo rule = blazeProjectData.ruleMap.get(ruleKey);
-          if (rule == null) {
+          TargetIdeInfo target = blazeProjectData.targetMap.get(targetKey);
+          if (target == null) {
             continue;
           }
-          if (!rule.kindIsOneOf(Kind.ANDROID_BINARY, Kind.ANDROID_TEST)) {
+          if (!target.kindIsOneOf(Kind.ANDROID_BINARY, Kind.ANDROID_TEST)) {
             continue;
           }
 
-          String moduleName = moduleNameForAndroidModule(ruleKey);
+          String moduleName = moduleNameForAndroidModule(targetKey);
           Module module = moduleEditor.createModule(moduleName, StdModuleTypes.JAVA);
           ModifiableRootModel modifiableRootModel = moduleEditor.editModule(module);
-          updateAndroidRuleModule(
+          updateAndroidTargetModule(
               project,
               workspaceRoot,
               blazeProjectData.artifactLocationDecoder,
               androidSdkPlatform,
-              rule,
+              target,
               module,
               modifiableRootModel,
               null);
@@ -206,9 +207,9 @@
 
   /** Ensures a suitable module exists for the given android target. */
   @Nullable
-  public static Module ensureRunConfigurationModule(Project project, Label target) {
-    RuleKey ruleKey = RuleKey.forPlainTarget(target);
-    String moduleName = moduleNameForAndroidModule(ruleKey);
+  public static Module ensureRunConfigurationModule(Project project, Label label) {
+    TargetKey targetKey = TargetKey.forPlainTarget(label);
+    String moduleName = moduleNameForAndroidModule(targetKey);
     Module module = ModuleManager.getInstance(project).findModuleByName(moduleName);
     if (module != null) {
       return module;
@@ -224,11 +225,11 @@
     if (androidSdkPlatform == null) {
       return null;
     }
-    RuleIdeInfo rule = blazeProjectData.ruleMap.get(ruleKey);
-    if (rule == null) {
+    TargetIdeInfo target = blazeProjectData.targetMap.get(targetKey);
+    if (target == null) {
       return null;
     }
-    if (rule.androidRuleIdeInfo == null) {
+    if (target.androidIdeInfo == null) {
       return null;
     }
     // We can't run a write action outside the dispatch thread, and can't
@@ -245,12 +246,12 @@
     ApplicationManager.getApplication()
         .runWriteAction(
             () -> {
-              updateAndroidRuleModule(
+              updateAndroidTargetModule(
                   project,
                   workspaceRoot,
                   blazeProjectData.artifactLocationDecoder,
                   androidSdkPlatform,
-                  rule,
+                  target,
                   newModule,
                   modifiableRootModel,
                   null);
@@ -259,8 +260,8 @@
     return newModule;
   }
 
-  public static String moduleNameForAndroidModule(RuleKey ruleKey) {
-    return ruleKey
+  public static String moduleNameForAndroidModule(TargetKey targetKey) {
+    return targetKey
         .toString()
         .substring(2) // Skip initial "//"
         .replace('/', '.')
@@ -289,12 +290,12 @@
   }
 
   /** Updates a module from an android rule. */
-  private static void updateAndroidRuleModule(
+  private static void updateAndroidTargetModule(
       Project project,
       WorkspaceRoot workspaceRoot,
       ArtifactLocationDecoder artifactLocationDecoder,
       AndroidSdkPlatform androidSdkPlatform,
-      RuleIdeInfo rule,
+      TargetIdeInfo target,
       Module module,
       ModifiableRootModel modifiableRootModel,
       @Nullable AndroidResourceModule androidResourceModule) {
@@ -308,16 +309,16 @@
             ? artifactLocationDecoder.decodeAll(androidResourceModule.transitiveResources)
             : ImmutableList.of();
 
-    AndroidRuleIdeInfo androidRuleIdeInfo = rule.androidRuleIdeInfo;
-    assert androidRuleIdeInfo != null;
+    AndroidIdeInfo androidIdeInfo = target.androidIdeInfo;
+    assert androidIdeInfo != null;
 
-    File moduleDirectory = workspaceRoot.fileForPath(rule.label.blazePackage());
-    ArtifactLocation manifestArtifactLocation = androidRuleIdeInfo.manifest;
+    File moduleDirectory = workspaceRoot.fileForPath(target.key.label.blazePackage());
+    ArtifactLocation manifestArtifactLocation = androidIdeInfo.manifest;
     File manifest =
         manifestArtifactLocation != null
             ? artifactLocationDecoder.decode(manifestArtifactLocation)
             : new File(moduleDirectory, "AndroidManifest.xml");
-    String resourceJavaPackage = androidRuleIdeInfo.resourceJavaPackage;
+    String resourceJavaPackage = androidIdeInfo.resourceJavaPackage;
     ResourceModuleContentRootCustomizer.setupContentRoots(modifiableRootModel, resources);
 
     createAndroidModel(
diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateMigrationTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateMigrationTest.java
new file mode 100644
index 0000000..0a222a7
--- /dev/null
+++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateMigrationTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.idea.blaze.android.run;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.idea.blaze.android.BlazeAndroidIntegrationTestCase;
+import java.io.StringReader;
+import java.util.List;
+import org.jdom.Element;
+import org.jdom.input.SAXBuilder;
+import org.jdom.output.Format;
+import org.jdom.output.XMLOutputter;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for migrating the storage of deploy target state and debugger state in {@link
+ * BlazeAndroidRunConfigurationCommonState} TODO Introduced in 1.12, remove in 1.14 when the
+ * migration code in BlazeAndroidRunConfigurationCommonState is removed.
+ */
+@RunWith(JUnit4.class)
+public class BlazeAndroidRunConfigurationCommonStateMigrationTest
+    extends BlazeAndroidIntegrationTestCase {
+
+  private static final String DEPLOY_TARGET_STATES_RAW_XML =
+      "<android-deploy-target-states>"
+          + "  <option name=\"USE_LAST_SELECTED_DEVICE\" value=\"true\" />"
+          + "  <option name=\"PREFERRED_AVD\" value=\"some avd\" />"
+          + "</android-deploy-target-states>";
+  private static final String DEBUGGER_STATE_AUTO_RAW_XML =
+      "<Auto>"
+          + "  <option name=\"USE_JAVA_AWARE_DEBUGGER\" value=\"true\" />"
+          + "  <option name=\"WORKING_DIR\" value=\"/some/directory\" />"
+          + "  <option name=\"TARGET_LOGGING_CHANNELS\" value=\"some channels\" />"
+          + "</Auto>";
+  private static final String DEBUGGER_STATE_NATIVE_RAW_XML =
+      "<Native>"
+          + "  <option name=\"USE_JAVA_AWARE_DEBUGGER\" value=\"false\" />"
+          + "  <option name=\"WORKING_DIR\" value=\"\" />"
+          + "  <option name=\"TARGET_LOGGING_CHANNELS\""
+          + "          value=\"lldb process:gdb-remote packets\" />"
+          + "</Native>";
+  private static final String DEBUGGER_STATE_JAVA_RAW_XML = "<Java />";
+  private static final String DEBUGGER_STATE_HYBRID_RAW_XML =
+      "<Hybrid>"
+          + "  <option name=\"USE_JAVA_AWARE_DEBUGGER\" value=\"true\" />"
+          + "  <option name=\"WORKING_DIR\" value=\"\" />"
+          + "  <option name=\"TARGET_LOGGING_CHANNELS\""
+          + "          value=\"lldb process:gdb-remote packets\" />"
+          + "</Hybrid>";
+  private static final String DEBUGGER_STATE_BLAZE_AUTO_RAW_XML =
+      "<BlazeAuto>"
+          + "  <option name=\"USE_JAVA_AWARE_DEBUGGER\" value=\"false\" />"
+          + "  <option name=\"WORKING_DIR\" value=\"/some/other/directory\" />"
+          + "  <option name=\"TARGET_LOGGING_CHANNELS\" value=\"some other channels\" />"
+          + "</BlazeAuto>";
+
+  private BlazeAndroidRunConfigurationCommonState state;
+  private SAXBuilder saxBuilder;
+  private XMLOutputter xmlOutputter;
+
+  @Before
+  public final void doSetup() throws Exception {
+    state = new BlazeAndroidRunConfigurationCommonState(buildSystem().getName(), false);
+    saxBuilder = new SAXBuilder();
+    xmlOutputter = new XMLOutputter(Format.getCompactFormat());
+  }
+
+  private String formatRawXml(String rawXml) throws Exception {
+    Element element =
+        saxBuilder.build(new StringReader("<?xml version=\"1.0\"?>" + rawXml)).getRootElement();
+    return xmlOutputter.outputString(element);
+  }
+
+  @Test
+  public void readAndWriteShouldMigrate() throws Exception {
+    String oldXml =
+        "<?xml version=\"1.0\"?>"
+            + "<configuration blaze-native-debug=\"true\">"
+            + "  <blaze-user-flag>--flag1</blaze-user-flag>"
+            + "  <blaze-user-flag>--flag2</blaze-user-flag>"
+            + "  <option name=\"USE_LAST_SELECTED_DEVICE\" value=\"true\" />"
+            + "  <option name=\"PREFERRED_AVD\" value=\"some avd\" />"
+            + DEBUGGER_STATE_AUTO_RAW_XML
+            + DEBUGGER_STATE_NATIVE_RAW_XML
+            + DEBUGGER_STATE_JAVA_RAW_XML
+            + DEBUGGER_STATE_HYBRID_RAW_XML
+            + DEBUGGER_STATE_BLAZE_AUTO_RAW_XML
+            + "</configuration>";
+    Element oldElement = saxBuilder.build(new StringReader(oldXml)).getRootElement();
+
+    state.readExternal(oldElement);
+    Element migratedElement = new Element("configuration");
+    state.writeExternal(migratedElement);
+
+    assertThat(migratedElement.getChildren()).hasSize(4);
+    assertThat(migratedElement.getAttribute("blaze-native-debug").getValue()).isEqualTo("true");
+
+    List<Element> flagElements = migratedElement.getChildren("blaze-user-flag");
+    assertThat(flagElements).hasSize(2);
+    assertThat(flagElements.get(0).getText()).isEqualTo("--flag1");
+    assertThat(flagElements.get(1).getText()).isEqualTo("--flag2");
+
+    Element deployTargetStatesElement = migratedElement.getChild("android-deploy-target-states");
+    assertThat(xmlOutputter.outputString(deployTargetStatesElement))
+        .isEqualTo(formatRawXml(DEPLOY_TARGET_STATES_RAW_XML));
+
+    Element debuggerStatesElement = migratedElement.getChild("android-debugger-states");
+    assertThat(debuggerStatesElement.getChildren()).hasSize(5);
+    Element debuggerStateElement = debuggerStatesElement.getChild("Auto");
+    assertThat(xmlOutputter.outputString(debuggerStateElement))
+        .isEqualTo(formatRawXml(DEBUGGER_STATE_AUTO_RAW_XML));
+    debuggerStateElement = debuggerStatesElement.getChild("Native");
+    assertThat(xmlOutputter.outputString(debuggerStateElement))
+        .isEqualTo(formatRawXml(DEBUGGER_STATE_NATIVE_RAW_XML));
+    debuggerStateElement = debuggerStatesElement.getChild("Java");
+    assertThat(xmlOutputter.outputString(debuggerStateElement))
+        .isEqualTo(formatRawXml(DEBUGGER_STATE_JAVA_RAW_XML));
+    debuggerStateElement = debuggerStatesElement.getChild("Hybrid");
+    assertThat(xmlOutputter.outputString(debuggerStateElement))
+        .isEqualTo(formatRawXml(DEBUGGER_STATE_HYBRID_RAW_XML));
+    debuggerStateElement = debuggerStatesElement.getChild("BlazeAuto");
+    assertThat(xmlOutputter.outputString(debuggerStateElement))
+        .isEqualTo(formatRawXml(DEBUGGER_STATE_BLAZE_AUTO_RAW_XML));
+  }
+
+  @Test
+  public void readAndWriteShouldRemoveExtraElements() throws Exception {
+    String oldXml =
+        "<?xml version=\"1.0\"?>"
+            + "<configuration blaze-native-debug=\"true\">"
+            + "  <blaze-user-flag>--flag1</blaze-user-flag>"
+            + "  <blaze-user-flag>--flag2</blaze-user-flag>"
+            + "  <option name=\"USE_LAST_SELECTED_DEVICE\" value=\"true\" />"
+            + "  <option name=\"PREFERRED_AVD\" value=\"some avd\" />"
+            + DEBUGGER_STATE_AUTO_RAW_XML
+            + DEBUGGER_STATE_NATIVE_RAW_XML
+            + DEBUGGER_STATE_JAVA_RAW_XML
+            + DEBUGGER_STATE_HYBRID_RAW_XML
+            + DEBUGGER_STATE_BLAZE_AUTO_RAW_XML
+            + "  <option name=\"USE_LAST_SELECTED_DEVICE\" value=\"true\" />"
+            + "  <option name=\"PREFERRED_AVD\" value=\"some avd\" />"
+            + DEBUGGER_STATE_AUTO_RAW_XML
+            + DEBUGGER_STATE_NATIVE_RAW_XML
+            + DEBUGGER_STATE_JAVA_RAW_XML
+            + DEBUGGER_STATE_HYBRID_RAW_XML
+            + DEBUGGER_STATE_BLAZE_AUTO_RAW_XML
+            + "</configuration>";
+    Element oldElement = saxBuilder.build(new StringReader(oldXml)).getRootElement();
+
+    state.readExternal(oldElement);
+    Element migratedElement = new Element("configuration");
+    state.writeExternal(migratedElement);
+
+    assertThat(migratedElement.getChildren()).hasSize(4);
+    List<Element> flagElements = migratedElement.getChildren("blaze-user-flag");
+    assertThat(flagElements).hasSize(2);
+
+    Element deployTargetStatesElement = migratedElement.getChild("android-deploy-target-states");
+    assertThat(xmlOutputter.outputString(deployTargetStatesElement))
+        .isEqualTo(formatRawXml(DEPLOY_TARGET_STATES_RAW_XML));
+
+    Element debuggerStatesElement = migratedElement.getChild("android-debugger-states");
+    assertThat(debuggerStatesElement.getChildren()).hasSize(5);
+  }
+}
diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateTest.java
new file mode 100644
index 0000000..1964580
--- /dev/null
+++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/BlazeAndroidRunConfigurationCommonStateTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.idea.blaze.android.run;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.android.BlazeAndroidIntegrationTestCase;
+import com.google.idea.blaze.android.cppapi.NdkSupport;
+import com.google.idea.common.experiments.ExperimentService;
+import com.google.idea.common.experiments.MockExperimentService;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.WriteExternalException;
+import org.jdom.Element;
+import org.jdom.output.Format;
+import org.jdom.output.XMLOutputter;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link BlazeAndroidRunConfigurationCommonState}. */
+@RunWith(JUnit4.class)
+public class BlazeAndroidRunConfigurationCommonStateTest extends BlazeAndroidIntegrationTestCase {
+
+  private BlazeAndroidRunConfigurationCommonState state;
+
+  @Before
+  public final void doSetup() {
+    MockExperimentService experimentService = new MockExperimentService();
+    registerApplicationComponent(ExperimentService.class, experimentService);
+    // BlazeAndroidRunConfigurationCommonState.isNativeDebuggingEnabled() always
+    // returns false if this experiment is false.
+    experimentService.setExperiment(NdkSupport.NDK_SUPPORT, true);
+
+    state = new BlazeAndroidRunConfigurationCommonState(buildSystem().getName(), false);
+  }
+
+  @Test
+  public void readAndWriteShouldMatch() throws InvalidDataException, WriteExternalException {
+    state.setUserFlags(ImmutableList.of("--flag1", "--flag2"));
+    state.setNativeDebuggingEnabled(true);
+
+    Element element = new Element("test");
+    state.writeExternal(element);
+    BlazeAndroidRunConfigurationCommonState readState =
+        new BlazeAndroidRunConfigurationCommonState(buildSystem().getName(), false);
+    readState.readExternal(element);
+
+    assertThat(readState.getUserFlags()).containsExactly("--flag1", "--flag2").inOrder();
+    assertThat(readState.isNativeDebuggingEnabled()).isTrue();
+  }
+
+  @Test
+  public void readAndWriteShouldHandleNulls() throws InvalidDataException, WriteExternalException {
+    Element element = new Element("test");
+    state.writeExternal(element);
+    BlazeAndroidRunConfigurationCommonState readState =
+        new BlazeAndroidRunConfigurationCommonState(buildSystem().getName(), false);
+    readState.readExternal(element);
+
+    assertThat(readState.getUserFlags()).isEqualTo(state.getUserFlags());
+    assertThat(readState.isNativeDebuggingEnabled()).isEqualTo(state.isNativeDebuggingEnabled());
+  }
+
+  @Test
+  public void readShouldOmitEmptyFlags() throws InvalidDataException, WriteExternalException {
+    state.setUserFlags(ImmutableList.of("hi ", "", "I'm", " ", "\t", "Josh\r\n", "\n"));
+
+    Element element = new Element("test");
+    state.writeExternal(element);
+    BlazeAndroidRunConfigurationCommonState readState =
+        new BlazeAndroidRunConfigurationCommonState(buildSystem().getName(), false);
+    readState.readExternal(element);
+
+    assertThat(readState.getUserFlags()).containsExactly("hi", "I'm", "Josh").inOrder();
+  }
+
+  @Test
+  public void repeatedWriteShouldNotChangeElement() throws WriteExternalException {
+    final XMLOutputter xmlOutputter = new XMLOutputter(Format.getCompactFormat());
+
+    state.setUserFlags(ImmutableList.of("--flag1", "--flag2"));
+    state.setNativeDebuggingEnabled(true);
+
+    Element firstWrite = new Element("test");
+    state.writeExternal(firstWrite);
+    Element secondWrite = firstWrite.clone();
+    state.writeExternal(secondWrite);
+
+    assertThat(xmlOutputter.outputString(secondWrite))
+        .isEqualTo(xmlOutputter.outputString(firstWrite));
+  }
+}
diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateTest.java
new file mode 100644
index 0000000..76cd2e5
--- /dev/null
+++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/binary/BlazeAndroidBinaryRunConfigurationStateTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.idea.blaze.android.run.binary;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.android.BlazeAndroidIntegrationTestCase;
+import com.google.idea.blaze.android.cppapi.NdkSupport;
+import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState;
+import com.google.idea.blaze.base.run.state.RunConfigurationStateEditor;
+import com.google.idea.common.experiments.ExperimentService;
+import com.google.idea.common.experiments.MockExperimentService;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.WriteExternalException;
+import org.jdom.Element;
+import org.jdom.output.Format;
+import org.jdom.output.XMLOutputter;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link BlazeAndroidBinaryRunConfigurationState}. */
+@RunWith(JUnit4.class)
+public class BlazeAndroidBinaryRunConfigurationStateTest extends BlazeAndroidIntegrationTestCase {
+
+  private BlazeAndroidBinaryRunConfigurationState state;
+
+  @Before
+  public final void doSetup() {
+    MockExperimentService experimentService = new MockExperimentService();
+    registerApplicationComponent(ExperimentService.class, experimentService);
+    // BlazeAndroidRunConfigurationCommonState.isNativeDebuggingEnabled() always
+    // returns false if this experiment is false.
+    experimentService.setExperiment(NdkSupport.NDK_SUPPORT, true);
+
+    state = new BlazeAndroidBinaryRunConfigurationState(buildSystem().getName());
+  }
+
+  @Test
+  public void readAndWriteShouldMatch() throws InvalidDataException, WriteExternalException {
+    BlazeAndroidRunConfigurationCommonState commonState = state.getCommonState();
+    commonState.setUserFlags(ImmutableList.of("--flag1", "--flag2"));
+    commonState.setNativeDebuggingEnabled(true);
+
+    state.setActivityClass("com.example.TestActivity");
+    state.setMode(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY);
+    state.setMobileInstall(true);
+    state.setUseSplitApksIfPossible(false);
+    state.setInstantRun(true);
+    state.setUseWorkProfileIfPresent(true);
+    state.setUserId(2);
+    state.setDeepLink("http://deeplink");
+
+    Element element = new Element("test");
+    state.writeExternal(element);
+    BlazeAndroidBinaryRunConfigurationState readState =
+        new BlazeAndroidBinaryRunConfigurationState(buildSystem().getName());
+    readState.readExternal(element);
+
+    BlazeAndroidRunConfigurationCommonState readCommonState = readState.getCommonState();
+    assertThat(readCommonState.getUserFlags()).containsExactly("--flag1", "--flag2").inOrder();
+    assertThat(readCommonState.isNativeDebuggingEnabled()).isTrue();
+
+    assertThat(readState.getActivityClass()).isEqualTo("com.example.TestActivity");
+    assertThat(readState.getMode())
+        .isEqualTo(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY);
+    assertThat(readState.mobileInstall()).isTrue();
+    assertThat(readState.useSplitApksIfPossible()).isFalse();
+    assertThat(readState.instantRun()).isTrue();
+    assertThat(readState.useWorkProfileIfPresent()).isTrue();
+    assertThat(readState.getUserId()).isEqualTo(2);
+    assertThat(readState.getDeepLink()).isEqualTo("http://deeplink");
+  }
+
+  @Test
+  public void readAndWriteShouldHandleNulls() throws InvalidDataException, WriteExternalException {
+    Element element = new Element("test");
+    state.writeExternal(element);
+    BlazeAndroidBinaryRunConfigurationState readState =
+        new BlazeAndroidBinaryRunConfigurationState(buildSystem().getName());
+    readState.readExternal(element);
+
+    BlazeAndroidRunConfigurationCommonState commonState = state.getCommonState();
+    BlazeAndroidRunConfigurationCommonState readCommonState = readState.getCommonState();
+    assertThat(readCommonState.getUserFlags()).isEqualTo(commonState.getUserFlags());
+    assertThat(readCommonState.isNativeDebuggingEnabled())
+        .isEqualTo(commonState.isNativeDebuggingEnabled());
+
+    assertThat(readState.getActivityClass()).isEqualTo(state.getActivityClass());
+    assertThat(readState.getMode()).isEqualTo(state.getMode());
+    assertThat(readState.mobileInstall()).isEqualTo(state.mobileInstall());
+    assertThat(readState.useSplitApksIfPossible()).isEqualTo(state.useSplitApksIfPossible());
+    assertThat(readState.instantRun()).isEqualTo(state.instantRun());
+    assertThat(readState.useWorkProfileIfPresent()).isEqualTo(state.useWorkProfileIfPresent());
+    assertThat(readState.getUserId()).isEqualTo(state.getUserId());
+    assertThat(readState.getDeepLink()).isEqualTo(state.getDeepLink());
+  }
+
+  @Test
+  public void repeatedWriteShouldNotChangeElement() throws WriteExternalException {
+    final XMLOutputter xmlOutputter = new XMLOutputter(Format.getCompactFormat());
+
+    BlazeAndroidRunConfigurationCommonState commonState = state.getCommonState();
+    commonState.setUserFlags(ImmutableList.of("--flag1", "--flag2"));
+    commonState.setNativeDebuggingEnabled(true);
+
+    state.setActivityClass("com.example.TestActivity");
+    state.setMode(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY);
+    state.setMobileInstall(true);
+    state.setUseSplitApksIfPossible(false);
+    state.setInstantRun(true);
+    state.setUseWorkProfileIfPresent(true);
+    state.setUserId(2);
+    state.setDeepLink("http://deeplink");
+
+    Element firstWrite = new Element("test");
+    state.writeExternal(firstWrite);
+    Element secondWrite = firstWrite.clone();
+    state.writeExternal(secondWrite);
+
+    assertThat(xmlOutputter.outputString(secondWrite))
+        .isEqualTo(xmlOutputter.outputString(firstWrite));
+  }
+
+  @Test
+  public void editorApplyToAndResetFromShouldMatch() throws ConfigurationException {
+    RunConfigurationStateEditor editor = state.getEditor(getProject());
+
+    BlazeAndroidRunConfigurationCommonState commonState = state.getCommonState();
+    commonState.setUserFlags(ImmutableList.of("--flag1", "--flag2"));
+    commonState.setNativeDebuggingEnabled(true);
+
+    state.setActivityClass("com.example.TestActivity");
+    state.setMode(BlazeAndroidBinaryRunConfigurationState.LAUNCH_SPECIFIC_ACTIVITY);
+    state.setMobileInstall(true);
+    state.setUseSplitApksIfPossible(false);
+    state.setInstantRun(true);
+    state.setUseWorkProfileIfPresent(true);
+    state.setUserId(2);
+    // We don't test DeepLink because it is not exposed in the editor.
+    //state.setDeepLink("http://deeplink");
+
+    editor.resetEditorFrom(state);
+    BlazeAndroidBinaryRunConfigurationState readState =
+        new BlazeAndroidBinaryRunConfigurationState(buildSystem().getName());
+    editor.applyEditorTo(readState);
+
+    BlazeAndroidRunConfigurationCommonState readCommonState = readState.getCommonState();
+    assertThat(readCommonState.getUserFlags()).isEqualTo(commonState.getUserFlags());
+    assertThat(readCommonState.isNativeDebuggingEnabled())
+        .isEqualTo(commonState.isNativeDebuggingEnabled());
+
+    assertThat(readState.getActivityClass()).isEqualTo(state.getActivityClass());
+    assertThat(readState.getMode()).isEqualTo(state.getMode());
+    assertThat(readState.mobileInstall()).isEqualTo(state.mobileInstall());
+    assertThat(readState.useSplitApksIfPossible()).isEqualTo(state.useSplitApksIfPossible());
+    assertThat(readState.instantRun()).isEqualTo(state.instantRun());
+    assertThat(readState.useWorkProfileIfPresent()).isEqualTo(state.useWorkProfileIfPresent());
+    assertThat(readState.getUserId()).isEqualTo(state.getUserId());
+    // We don't test DeepLink because it is not exposed in the editor.
+    //assertThat(readState.getDeepLink()).isEqualTo(state.getDeepLink());
+  }
+
+  @Test
+  public void editorApplyToAndResetFromShouldHandleNulls() throws ConfigurationException {
+    RunConfigurationStateEditor editor = state.getEditor(getProject());
+
+    editor.resetEditorFrom(state);
+    BlazeAndroidBinaryRunConfigurationState readState =
+        new BlazeAndroidBinaryRunConfigurationState(buildSystem().getName());
+    editor.applyEditorTo(readState);
+
+    BlazeAndroidRunConfigurationCommonState commonState = state.getCommonState();
+    BlazeAndroidRunConfigurationCommonState readCommonState = readState.getCommonState();
+    assertThat(readCommonState.getUserFlags()).isEqualTo(commonState.getUserFlags());
+    assertThat(readCommonState.isNativeDebuggingEnabled())
+        .isEqualTo(commonState.isNativeDebuggingEnabled());
+
+    assertThat(readState.getActivityClass()).isEqualTo(state.getActivityClass());
+    assertThat(readState.getMode()).isEqualTo(state.getMode());
+    assertThat(readState.mobileInstall()).isEqualTo(state.mobileInstall());
+    assertThat(readState.useSplitApksIfPossible()).isEqualTo(state.useSplitApksIfPossible());
+    assertThat(readState.instantRun()).isEqualTo(state.instantRun());
+    assertThat(readState.useWorkProfileIfPresent()).isEqualTo(state.useWorkProfileIfPresent());
+    assertThat(readState.getUserId()).isEqualTo(state.getUserId());
+    // We don't test DeepLink because it is not exposed in the editor.
+    //assertThat(readState.getDeepLink()).isEqualTo(state.getDeepLink());
+  }
+}
diff --git a/aswb/tests/integrationtests/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationStateTest.java b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationStateTest.java
new file mode 100644
index 0000000..f04a14e
--- /dev/null
+++ b/aswb/tests/integrationtests/com/google/idea/blaze/android/run/test/BlazeAndroidTestRunConfigurationStateTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.android.run.test;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.android.BlazeAndroidIntegrationTestCase;
+import com.google.idea.blaze.android.cppapi.NdkSupport;
+import com.google.idea.blaze.android.run.BlazeAndroidRunConfigurationCommonState;
+import com.google.idea.blaze.base.run.state.RunConfigurationStateEditor;
+import com.google.idea.common.experiments.ExperimentService;
+import com.google.idea.common.experiments.MockExperimentService;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.WriteExternalException;
+import org.jdom.Element;
+import org.jdom.output.Format;
+import org.jdom.output.XMLOutputter;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link BlazeAndroidTestRunConfigurationState}. */
+@RunWith(JUnit4.class)
+public class BlazeAndroidTestRunConfigurationStateTest extends BlazeAndroidIntegrationTestCase {
+
+  private BlazeAndroidTestRunConfigurationState state;
+
+  @Before
+  public final void doSetup() {
+    MockExperimentService experimentService = new MockExperimentService();
+    registerApplicationComponent(ExperimentService.class, experimentService);
+    // BlazeAndroidRunConfigurationCommonState.isNativeDebuggingEnabled() always
+    // returns false if this experiment is false.
+    experimentService.setExperiment(NdkSupport.NDK_SUPPORT, true);
+
+    state = new BlazeAndroidTestRunConfigurationState(buildSystem().getName());
+  }
+
+  @Test
+  public void readAndWriteShouldMatch() throws InvalidDataException, WriteExternalException {
+    BlazeAndroidRunConfigurationCommonState commonState = state.getCommonState();
+    commonState.setUserFlags(ImmutableList.of("--flag1", "--flag2"));
+    commonState.setNativeDebuggingEnabled(true);
+
+    state.setTestingType(BlazeAndroidTestRunConfigurationState.TEST_METHOD);
+    state.setInstrumentationRunnerClass("com.example.TestRunner");
+    state.setMethodName("fooMethod");
+    state.setClassName("BarClass");
+    state.setPackageName("com.test.package.name");
+    state.setRunThroughBlaze(true);
+    state.setExtraOptions("--option");
+
+    Element element = new Element("test");
+    state.writeExternal(element);
+    BlazeAndroidTestRunConfigurationState readState =
+        new BlazeAndroidTestRunConfigurationState(buildSystem().getName());
+    readState.readExternal(element);
+
+    BlazeAndroidRunConfigurationCommonState readCommonState = readState.getCommonState();
+    assertThat(readCommonState.getUserFlags()).containsExactly("--flag1", "--flag2").inOrder();
+    assertThat(readCommonState.isNativeDebuggingEnabled()).isTrue();
+
+    assertThat(readState.getTestingType())
+        .isEqualTo(BlazeAndroidTestRunConfigurationState.TEST_METHOD);
+    assertThat(readState.getInstrumentationRunnerClass()).isEqualTo("com.example.TestRunner");
+    assertThat(readState.getMethodName()).isEqualTo("fooMethod");
+    assertThat(readState.getClassName()).isEqualTo("BarClass");
+    assertThat(readState.getPackageName()).isEqualTo("com.test.package.name");
+    assertThat(readState.isRunThroughBlaze()).isTrue();
+    assertThat(readState.getExtraOptions()).isEqualTo("--option");
+  }
+
+  @Test
+  public void readAndWriteShouldHandleNulls() throws InvalidDataException, WriteExternalException {
+    Element element = new Element("test");
+    state.writeExternal(element);
+    BlazeAndroidTestRunConfigurationState readState =
+        new BlazeAndroidTestRunConfigurationState(buildSystem().getName());
+    readState.readExternal(element);
+
+    BlazeAndroidRunConfigurationCommonState commonState = state.getCommonState();
+    BlazeAndroidRunConfigurationCommonState readCommonState = readState.getCommonState();
+    assertThat(readCommonState.getUserFlags()).isEqualTo(commonState.getUserFlags());
+    assertThat(readCommonState.isNativeDebuggingEnabled())
+        .isEqualTo(commonState.isNativeDebuggingEnabled());
+
+    assertThat(readState.getTestingType()).isEqualTo(state.getTestingType());
+    assertThat(readState.getInstrumentationRunnerClass())
+        .isEqualTo(state.getInstrumentationRunnerClass());
+    assertThat(readState.getMethodName()).isEqualTo(state.getMethodName());
+    assertThat(readState.getClassName()).isEqualTo(state.getClassName());
+    assertThat(readState.getPackageName()).isEqualTo(state.getPackageName());
+    assertThat(readState.isRunThroughBlaze()).isEqualTo(state.isRunThroughBlaze());
+    assertThat(readState.getExtraOptions()).isEqualTo(state.getExtraOptions());
+  }
+
+  @Test
+  public void repeatedWriteShouldNotChangeElement() throws WriteExternalException {
+    final XMLOutputter xmlOutputter = new XMLOutputter(Format.getCompactFormat());
+
+    BlazeAndroidRunConfigurationCommonState commonState = state.getCommonState();
+    commonState.setUserFlags(ImmutableList.of("--flag1", "--flag2"));
+    commonState.setNativeDebuggingEnabled(true);
+
+    state.setTestingType(BlazeAndroidTestRunConfigurationState.TEST_METHOD);
+    state.setInstrumentationRunnerClass("com.example.TestRunner");
+    state.setMethodName("fooMethod");
+    state.setClassName("BarClass");
+    state.setPackageName("com.test.package.name");
+    state.setRunThroughBlaze(true);
+    state.setExtraOptions("--option");
+
+    Element firstWrite = new Element("test");
+    state.writeExternal(firstWrite);
+    Element secondWrite = firstWrite.clone();
+    state.writeExternal(secondWrite);
+
+    assertThat(xmlOutputter.outputString(secondWrite))
+        .isEqualTo(xmlOutputter.outputString(firstWrite));
+  }
+
+  @Test
+  public void editorApplyToAndResetFromShouldMatch() throws ConfigurationException {
+    RunConfigurationStateEditor editor = state.getEditor(getProject());
+
+    BlazeAndroidRunConfigurationCommonState commonState = state.getCommonState();
+    commonState.setUserFlags(ImmutableList.of("--flag1", "--flag2"));
+    commonState.setNativeDebuggingEnabled(true);
+
+    state.setTestingType(BlazeAndroidTestRunConfigurationState.TEST_METHOD);
+    state.setInstrumentationRunnerClass("com.example.TestRunner");
+    state.setMethodName("fooMethod");
+    state.setClassName("BarClass");
+    state.setPackageName("com.test.package.name");
+    state.setRunThroughBlaze(true);
+    // We don't test ExtraOptions because it is not exposed in the editor.
+    //state.setExtraOptions("--option");
+
+    editor.resetEditorFrom(state);
+    BlazeAndroidTestRunConfigurationState readState =
+        new BlazeAndroidTestRunConfigurationState(buildSystem().getName());
+    editor.applyEditorTo(readState);
+
+    BlazeAndroidRunConfigurationCommonState readCommonState = readState.getCommonState();
+    assertThat(readCommonState.getUserFlags()).isEqualTo(commonState.getUserFlags());
+    assertThat(readCommonState.isNativeDebuggingEnabled())
+        .isEqualTo(commonState.isNativeDebuggingEnabled());
+
+    assertThat(readState.getTestingType()).isEqualTo(state.getTestingType());
+    assertThat(readState.getInstrumentationRunnerClass())
+        .isEqualTo(state.getInstrumentationRunnerClass());
+    assertThat(readState.getMethodName()).isEqualTo(state.getMethodName());
+    assertThat(readState.getClassName()).isEqualTo(state.getClassName());
+    assertThat(readState.getPackageName()).isEqualTo(state.getPackageName());
+    assertThat(readState.isRunThroughBlaze()).isEqualTo(state.isRunThroughBlaze());
+    // We don't test ExtraOptions because it is not exposed in the editor.
+    //assertThat(readState.getExtraOptions()).isEqualTo(state.getExtraOptions());
+  }
+
+  @Test
+  public void editorApplyToAndResetFromShouldHandleNulls() throws ConfigurationException {
+    RunConfigurationStateEditor editor = state.getEditor(getProject());
+
+    editor.resetEditorFrom(state);
+    BlazeAndroidTestRunConfigurationState readState =
+        new BlazeAndroidTestRunConfigurationState(buildSystem().getName());
+    editor.applyEditorTo(readState);
+
+    BlazeAndroidRunConfigurationCommonState commonState = state.getCommonState();
+    BlazeAndroidRunConfigurationCommonState readCommonState = readState.getCommonState();
+    assertThat(readCommonState.getUserFlags()).isEqualTo(commonState.getUserFlags());
+    assertThat(readCommonState.isNativeDebuggingEnabled())
+        .isEqualTo(commonState.isNativeDebuggingEnabled());
+
+    assertThat(readState.getTestingType()).isEqualTo(state.getTestingType());
+    assertThat(readState.getInstrumentationRunnerClass())
+        .isEqualTo(state.getInstrumentationRunnerClass());
+    assertThat(readState.getMethodName()).isEqualTo(state.getMethodName());
+    assertThat(readState.getClassName()).isEqualTo(state.getClassName());
+    assertThat(readState.getPackageName()).isEqualTo(state.getPackageName());
+    assertThat(readState.isRunThroughBlaze()).isEqualTo(state.isRunThroughBlaze());
+    // We don't test ExtraOptions because it is not exposed in the editor.
+    //assertThat(readState.getExtraOptions()).isEqualTo(state.getExtraOptions());
+  }
+}
diff --git a/aswb/tests/unittests/com/google/idea/blaze/android/sync/importer/BlazeAndroidWorkspaceImporterTest.java b/aswb/tests/unittests/com/google/idea/blaze/android/sync/importer/BlazeAndroidWorkspaceImporterTest.java
index 985db7b..1f16d2d 100644
--- a/aswb/tests/unittests/com/google/idea/blaze/android/sync/importer/BlazeAndroidWorkspaceImporterTest.java
+++ b/aswb/tests/unittests/com/google/idea/blaze/android/sync/importer/BlazeAndroidWorkspaceImporterTest.java
@@ -17,6 +17,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.idea.blaze.android.sync.BlazeAndroidJavaSyncAugmenter;
 import com.google.idea.blaze.android.sync.model.AndroidResourceModule;
@@ -25,17 +26,19 @@
 import com.google.idea.blaze.base.BlazeTestCase;
 import com.google.idea.blaze.base.async.executor.BlazeExecutor;
 import com.google.idea.blaze.base.async.executor.MockBlazeExecutor;
-import com.google.idea.blaze.base.ideinfo.AndroidRuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.JavaRuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.JavaIdeInfo;
 import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
-import com.google.idea.blaze.base.ideinfo.RuleMapBuilder;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
+import com.google.idea.blaze.base.ideinfo.TargetMapBuilder;
 import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
 import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.google.idea.blaze.base.model.primitives.WorkspaceType;
 import com.google.idea.blaze.base.projectview.ProjectView;
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
 import com.google.idea.blaze.base.projectview.section.ListSection;
@@ -48,6 +51,7 @@
 import com.google.idea.blaze.base.settings.BlazeImportSettings;
 import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
 import com.google.idea.blaze.base.sync.projectview.ImportRoots;
+import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
 import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
 import com.google.idea.common.experiments.ExperimentService;
 import com.google.idea.common.experiments.MockExperimentService;
@@ -92,13 +96,13 @@
   }
 
   BlazeAndroidImportResult importWorkspace(
-      WorkspaceRoot workspaceRoot, RuleMapBuilder ruleMapBuilder, ProjectView projectView) {
+      WorkspaceRoot workspaceRoot, TargetMapBuilder targetMapBuilder, ProjectView projectView) {
 
     ProjectViewSet projectViewSet = ProjectViewSet.builder().add(projectView).build();
 
     BlazeAndroidWorkspaceImporter workspaceImporter =
         new BlazeAndroidWorkspaceImporter(
-            project, context, workspaceRoot, projectViewSet, ruleMapBuilder.build());
+            project, context, workspaceRoot, projectViewSet, targetMapBuilder.build());
 
     return workspaceImporter.importWorkspace();
   }
@@ -115,54 +119,54 @@
             .build();
 
     /** Deps are project -> lib0 -> lib1 -> shared project -> shared */
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/apps/example/lib0:lib0")
                     .setKind("android_library")
                     .setBuildFile(source("java/apps/example/lib0/BUILD"))
                     .addSource(source("java/apps/example/lib0/SharedActivity.java"))
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/apps/example/lib0/AndroidManifest.xml"))
                             .addResource(source("java/apps/example/lib0/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.apps.example.lib0"))
                     .addDependency("//java/apps/example/lib1:lib1")
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("java/apps/example/lib0/lib0.jar"))
                                     .setClassJar(gen("java/apps/example/lib0/lib0.jar")))))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/apps/example/lib1:lib1")
                     .setKind("android_library")
                     .setBuildFile(source("java/apps/example/lib1/BUILD"))
                     .addSource(source("java/apps/example/lib1/SharedActivity.java"))
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/apps/example/lib1/AndroidManifest.xml"))
                             .addResource(source("java/apps/example/lib1/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.apps.example.lib1"))
                     .addDependency("//java/libraries/shared:shared")
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("java/apps/example/lib1/lib1.jar"))
                                     .setClassJar(gen("java/apps/example/lib1/lib1.jar")))))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/apps/example:example_debug")
                     .setKind("android_binary")
                     .setBuildFile(source("java/apps/example/BUILD"))
                     .addSource(source("java/apps/example/MainActivity.java"))
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/apps/example/AndroidManifest.xml"))
                             .addResource(source("java/apps/example/res"))
                             .setGenerateResourceClass(true)
@@ -170,38 +174,38 @@
                     .addDependency("//java/apps/example/lib0:lib0")
                     .addDependency("//java/libraries/shared:shared")
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("java/apps/example/example_debug.jar"))
                                     .setClassJar(gen("java/apps/example/example_debug.jar")))))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/libraries/shared:shared")
                     .setBuildFile(source("java/libraries/shared/BUILD"))
                     .setKind("android_library")
                     .addSource(source("java/libraries/shared/SharedActivity.java"))
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/libraries/shared/AndroidManifest.xml"))
                             .addResource(source("java/libraries/shared/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.libraries.shared"))
                     .setBuildFile(source("java/libraries/shared/BUILD"))
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("java/libraries/shared.jar"))
                                     .setClassJar(gen("java/libraries/shared.jar")))));
 
-    BlazeAndroidImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeAndroidImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     errorCollector.assertNoIssues();
 
     assertThat(result.androidResourceModules)
         .containsExactly(
             AndroidResourceModule.builder(
-                    RuleKey.forPlainTarget(new Label("//java/apps/example:example_debug")))
+                    TargetKey.forPlainTarget(new Label("//java/apps/example:example_debug")))
                 .addResourceAndTransitiveResource(source("java/apps/example/res"))
                 .addTransitiveResource(source("java/apps/example/lib0/res"))
                 .addTransitiveResource(source("java/apps/example/lib1/res"))
@@ -211,7 +215,7 @@
                 .addTransitiveResourceDependency("//java/libraries/shared:shared")
                 .build(),
             AndroidResourceModule.builder(
-                    RuleKey.forPlainTarget(new Label("//java/apps/example/lib0:lib0")))
+                    TargetKey.forPlainTarget(new Label("//java/apps/example/lib0:lib0")))
                 .addResourceAndTransitiveResource(source("java/apps/example/lib0/res"))
                 .addTransitiveResource(source("java/apps/example/lib1/res"))
                 .addTransitiveResource(source("java/libraries/shared/res"))
@@ -219,7 +223,7 @@
                 .addTransitiveResourceDependency("//java/libraries/shared:shared")
                 .build(),
             AndroidResourceModule.builder(
-                    RuleKey.forPlainTarget(new Label("//java/apps/example/lib1:lib1")))
+                    TargetKey.forPlainTarget(new Label("//java/apps/example/lib1:lib1")))
                 .addResourceAndTransitiveResource(source("java/apps/example/lib1/res"))
                 .addTransitiveResource(source("java/libraries/shared/res"))
                 .addTransitiveResourceDependency("//java/libraries/shared:shared")
@@ -238,16 +242,16 @@
             .build();
 
     /** Deps are project -> lib0 (no res) -> lib1 (has res) \ -> lib2 (has res) */
-    RuleMapBuilder response =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder response =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/apps/example/lib0:lib0")
                     .setKind("android_library")
                     .setBuildFile(source("java/apps/example/lib0/BUILD"))
                     .addSource(source("java/apps/example/lib0/SharedActivity.java"))
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/apps/example/lib0/AndroidManifest.xml"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.apps.example.lib0")
@@ -259,7 +263,7 @@
                     .addDependency("//java/apps/example/lib1:lib1")
                     .addDependency("//java/apps/example/lib2:lib2")
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("java/apps/example/lib0/lib0.jar"))
@@ -270,14 +274,14 @@
                                         gen("java/apps/example/lib0/lib0_resources.jar"))
                                     .setClassJar(
                                         gen("java/apps/example/lib0/lib0_resources.jar")))))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/apps/example/lib1:lib1")
                     .setKind("android_library")
                     .setBuildFile(source("java/apps/example/lib1/BUILD"))
                     .addSource(source("java/apps/example/lib1/SharedActivity.java"))
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/apps/example/lib1/AndroidManifest.xml"))
                             .addResource(source("java/apps/example/lib1/res"))
                             .setGenerateResourceClass(true)
@@ -288,7 +292,7 @@
                                         gen("java/apps/example/lib1/li11_resources.jar"))
                                     .setClassJar(gen("java/apps/example/lib1/lib1_resources.jar"))))
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("java/apps/example/lib1/lib1.jar"))
@@ -299,14 +303,14 @@
                                         gen("java/apps/example/lib1/lib1_resources.jar"))
                                     .setClassJar(
                                         gen("java/apps/example/lib1/lib1_resources.jar")))))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/apps/example/lib2:lib2")
                     .setBuildFile(source("java/apps/example/lib2/BUILD"))
                     .setKind("android_library")
                     .addSource(source("java/apps/example/lib2/SharedActivity.java"))
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/apps/example/lib2/AndroidManifest.xml"))
                             .addResource(source("java/apps/example/lib2/res"))
                             .setGenerateResourceClass(true)
@@ -318,7 +322,7 @@
                                     .setClassJar(gen("java/apps/example/lib2/lib2_resources.jar"))))
                     .setBuildFile(source("java/apps/example/lib2/BUILD"))
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("java/apps/example/lib2/lib2.jar"))
@@ -329,27 +333,27 @@
                                         gen("java/apps/example/lib2/lib2_resources.jar"))
                                     .setClassJar(
                                         gen("java/apps/example/lib2/lib2_resources.jar")))))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/apps/example:example_debug")
                     .setKind("android_binary")
                     .setBuildFile(source("java/apps/example/BUILD"))
                     .addSource(source("java/apps/example/MainActivity.java"))
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/apps/example/AndroidManifest.xml"))
                             .addResource(source("java/apps/example/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.apps.example"))
                     .addDependency("//java/apps/example/lib0:lib0")
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("java/apps/example/example_debug.jar"))
                                     .setClassJar(gen("java/apps/example/example_debug.jar")))));
 
-    RuleMap ruleMap = response.build();
+    TargetMap targetMap = response.build();
     BlazeAndroidJavaSyncAugmenter syncAugmenter = new BlazeAndroidJavaSyncAugmenter();
     List<BlazeJarLibrary> jars = Lists.newArrayList();
     List<BlazeJarLibrary> genJars = Lists.newArrayList();
@@ -357,9 +361,12 @@
         ImportRoots.builder(workspaceRoot, BuildSystem.Blaze)
             .add(ProjectViewSet.builder().add(projectView).build())
             .build();
-    for (RuleIdeInfo rule : ruleMap.rules()) {
-      if (importRoots.importAsSource(rule.label)) {
-        syncAugmenter.addJarsForSourceRule(rule, jars, genJars);
+    WorkspaceLanguageSettings workspaceLanguageSettings =
+        new WorkspaceLanguageSettings(
+            WorkspaceType.ANDROID, ImmutableSet.of(LanguageClass.ANDROID, LanguageClass.JAVA));
+    for (TargetIdeInfo target : targetMap.targets()) {
+      if (importRoots.importAsSource(target.key.label)) {
+        syncAugmenter.addJarsForSourceTarget(workspaceLanguageSettings, target, jars, genJars);
       }
     }
 
@@ -380,16 +387,16 @@
                     .add(DirectoryEntry.include(new WorkspacePath("example"))))
             .build();
 
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//example:lib")
                     .setBuildFile(source("example/BUILD"))
                     .setKind("android_binary")
                     .addSource(source("example/MainActivity.java"))
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setResourceJavaPackage("example")
                             .setIdlJar(
                                 LibraryArtifact.builder()
@@ -398,7 +405,7 @@
                                     .build())
                             .setHasIdlSources(true)));
 
-    RuleMap ruleMap = ruleMapBuilder.build();
+    TargetMap targetMap = targetMapBuilder.build();
     BlazeAndroidJavaSyncAugmenter syncAugmenter = new BlazeAndroidJavaSyncAugmenter();
     List<BlazeJarLibrary> jars = Lists.newArrayList();
     List<BlazeJarLibrary> genJars = Lists.newArrayList();
@@ -406,9 +413,12 @@
         ImportRoots.builder(workspaceRoot, BuildSystem.Blaze)
             .add(ProjectViewSet.builder().add(projectView).build())
             .build();
-    for (RuleIdeInfo rule : ruleMap.rules()) {
-      if (importRoots.importAsSource(rule.label)) {
-        syncAugmenter.addJarsForSourceRule(rule, jars, genJars);
+    WorkspaceLanguageSettings workspaceLanguageSettings =
+        new WorkspaceLanguageSettings(
+            WorkspaceType.ANDROID, ImmutableSet.of(LanguageClass.ANDROID, LanguageClass.JAVA));
+    for (TargetIdeInfo target : targetMap.targets()) {
+      if (importRoots.importAsSource(target.key.label)) {
+        syncAugmenter.addJarsForSourceTarget(workspaceLanguageSettings, target, jars, genJars);
       }
     }
     assertThat(
@@ -429,40 +439,40 @@
                     .add(DirectoryEntry.include(new WorkspacePath("java/example"))))
             .build();
 
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/example:lib")
                     .setBuildFile(source("java/example/BUILD"))
                     .setKind("android_library")
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setLegacyResources(new Label("//java/example:resources"))
                             .setManifestFile(source("java/example/AndroidManifest.xml"))
                             .addResource(source("java/example/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.example"))
                     .build())
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/example:resources")
                     .setBuildFile(source("java/example/BUILD"))
                     .setKind("android_resources")
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/example/AndroidManifest.xml"))
                             .addResource(source("java/example/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.example"))
                     .build());
 
-    BlazeAndroidImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeAndroidImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     errorCollector.assertNoIssues();
     assertThat(result.androidResourceModules)
         .containsExactly(
             AndroidResourceModule.builder(
-                    RuleKey.forPlainTarget(new Label("//java/example:resources")))
+                    TargetKey.forPlainTarget(new Label("//java/example:resources")))
                 .addResourceAndTransitiveResource(source("java/example/res"))
                 .build());
   }
@@ -476,35 +486,35 @@
                     .add(DirectoryEntry.include(new WorkspacePath("java/example"))))
             .build();
 
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/example:lib")
                     .setBuildFile(source("java/example/BUILD"))
                     .setKind("android_library")
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/example/AndroidManifest.xml"))
                             .addResource(source("java/example/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.example"))
                     .addDependency("//java/example2:resources")
                     .build())
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/example2:resources")
                     .setBuildFile(source("java/example2/BUILD"))
                     .setKind("android_library")
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/example2/AndroidManifest.xml"))
                             .addResource(source("java/example2/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.example2"))
                     .build());
 
-    BlazeAndroidImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeAndroidImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     errorCollector.assertNoIssues();
     BlazeResourceLibrary library = result.resourceLibrary;
     assertThat(library).isNotNull();
@@ -525,40 +535,40 @@
                     .add(DirectoryEntry.include(new WorkspacePath("java/example"))))
             .build();
 
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/example:lib")
                     .setBuildFile(source("java/example/BUILD"))
                     .setKind("android_library")
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/example/AndroidManifest.xml"))
                             .addResource(source("java/example/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.example"))
                     .addDependency("//java/example2:resources")
                     .build())
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/example:lib2")
                     .setBuildFile(source("java/example2/BUILD"))
                     .setKind("android_library")
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/example2/AndroidManifest.xml"))
                             .addResource(source("java/example/res2"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.example"))
                     .build());
 
-    BlazeAndroidImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeAndroidImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     errorCollector.assertIssueContaining("Multiple R classes generated");
 
     assertThat(result.androidResourceModules)
         .containsExactly(
-            AndroidResourceModule.builder(RuleKey.forPlainTarget(new Label("//java/example:lib")))
+            AndroidResourceModule.builder(TargetKey.forPlainTarget(new Label("//java/example:lib")))
                 .addResourceAndTransitiveResource(source("java/example/res"))
                 .build());
   }
@@ -572,15 +582,15 @@
                     .add(DirectoryEntry.include(new WorkspacePath("java/example"))))
             .build();
 
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/example:lib")
                     .setBuildFile(source("java/example/BUILD"))
                     .setKind("android_library")
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/example/AndroidManifest.xml"))
                             .addResource(source("java/example/res"))
                             .addResource(gen("java/example/res"))
@@ -588,7 +598,7 @@
                             .setResourceJavaPackage("com.google.android.example"))
                     .build());
 
-    importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     errorCollector.assertIssueContaining("Dropping generated resource");
   }
 
diff --git a/aswb/tests/utils/integration/com/google/idea/blaze/android/AndroidTestCleanupHelper.java b/aswb/tests/utils/integration/com/google/idea/blaze/android/AndroidTestCleanupHelper.java
new file mode 100644
index 0000000..6847c67
--- /dev/null
+++ b/aswb/tests/utils/integration/com/google/idea/blaze/android/AndroidTestCleanupHelper.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.idea.blaze.android;
+
+import com.intellij.codeInsight.CodeInsightSettings;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.codeStyle.CodeStyleSchemes;
+import com.intellij.psi.codeStyle.CodeStyleSettings;
+import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
+import com.intellij.psi.impl.source.codeStyle.CodeStyleSchemeImpl;
+import com.intellij.util.xmlb.XmlSerializer;
+import org.jdom.Element;
+
+/**
+ * Helper class for cleaning up after Android Studio initialization. Android Studio changes code
+ * insight and style settings on initialization, so we need to revert these before tearing down the
+ * integration test fixture to avoid failing because of changed settings.<br>
+ * TODO: Remove once we build for a version where post-initialization insight and style settings are
+ * used in the settings check.
+ */
+public final class AndroidTestCleanupHelper {
+
+  public static void cleanUp(Project project) {
+    resetCodeInsightSettings();
+    resetCodeStyleSettings(project);
+  }
+
+  private static void resetCodeInsightSettings() {
+    // We can't just use CodeInsightSettings.getState(), because it excludes fields
+    // matching the default values, and thus wouldn't change anything when loaded.
+    Element codeInsightElement = new Element("state");
+    XmlSerializer.serializeInto(new CodeInsightSettings(), codeInsightElement);
+    CodeInsightSettings.getInstance().loadState(codeInsightElement);
+  }
+
+  private static void resetCodeStyleSettings(Project project) {
+    CodeStyleSettingsManager settingsManager = CodeStyleSettingsManager.getInstance(project);
+    if (settingsManager.USE_PER_PROJECT_SETTINGS && settingsManager.PER_PROJECT_SETTINGS != null) {
+      settingsManager.PER_PROJECT_SETTINGS = new CodeStyleSettings();
+    } else {
+      ((CodeStyleSchemeImpl)
+              CodeStyleSchemes.getInstance()
+                  .findPreferredScheme(settingsManager.PREFERRED_PROJECT_CODE_STYLE))
+          .setCodeStyleSettings(new CodeStyleSettings());
+    }
+  }
+}
diff --git a/aswb/tests/utils/integration/com/google/idea/blaze/android/AndroidTestSetupRule.java b/aswb/tests/utils/integration/com/google/idea/blaze/android/AndroidTestSetupRule.java
new file mode 100644
index 0000000..07fc1ac
--- /dev/null
+++ b/aswb/tests/utils/integration/com/google/idea/blaze/android/AndroidTestSetupRule.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.android;
+
+import com.intellij.testFramework.PlatformTestCase;
+import com.intellij.util.PlatformUtils;
+import org.junit.rules.ExternalResource;
+
+/**
+ * Runs before Android Studio integration tests, to ensure the AndroidStudio platform prefix is
+ * honored.
+ */
+public class AndroidTestSetupRule extends ExternalResource {
+
+  @Override
+  protected void before() throws Throwable {
+    // We require idea.platform.prefix to be defined before running tests.
+    // If we don't call this before setting up the test fixture, IntelliJ ignores
+    // the existing value and tries a limited set of candidate prefixes until it finds
+    // a matching descriptor for one of them. Notably, "AndroidStudio" is not a candidate.
+    // The first parameter doesn't matter in our case, so we pass a nonexistent class name.
+    PlatformTestCase.initPlatformPrefix("", System.getProperty(PlatformUtils.PLATFORM_PREFIX_KEY));
+    // TODO: Remove the above once we build for a version where "AndroidStudio" is a candidate.
+  }
+}
diff --git a/aswb/tests/utils/integration/com/google/idea/blaze/android/BlazeAndroidIntegrationTestCase.java b/aswb/tests/utils/integration/com/google/idea/blaze/android/BlazeAndroidIntegrationTestCase.java
new file mode 100644
index 0000000..106860a
--- /dev/null
+++ b/aswb/tests/utils/integration/com/google/idea/blaze/android/BlazeAndroidIntegrationTestCase.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.idea.blaze.android;
+
+import com.google.idea.blaze.base.BlazeIntegrationTestCase;
+import org.junit.After;
+import org.junit.Rule;
+
+/** Base test class for Blaze Android integration tests. */
+public abstract class BlazeAndroidIntegrationTestCase extends BlazeIntegrationTestCase {
+
+  @Rule public final AndroidTestSetupRule androidSetupRule = new AndroidTestSetupRule();
+
+  @After
+  public final void doTeardown() {
+    AndroidTestCleanupHelper.cleanUp(getProject());
+  }
+}
diff --git a/base/lib/proto_deps.jar b/base/lib/proto_deps.jar
deleted file mode 100755
index 4943ed3..0000000
--- a/base/lib/proto_deps.jar
+++ /dev/null
Binary files differ
diff --git a/base/resources/colorSchemes/BuildDefault.xml b/base/resources/colorSchemes/BuildDefault.xml
new file mode 100644
index 0000000..f8c9058
--- /dev/null
+++ b/base/resources/colorSchemes/BuildDefault.xml
@@ -0,0 +1,8 @@
+<?xml version='1.0'?>
+<list>
+  <option name="BUILD.BUILTIN_NAME">
+    <value>
+      <option name="FOREGROUND" value="80" />
+    </value>
+  </option>
+</list>
\ No newline at end of file
diff --git a/base/src/META-INF/blaze-base.xml b/base/src/META-INF/blaze-base.xml
index 1715ec6..ba5bbc3 100644
--- a/base/src/META-INF/blaze-base.xml
+++ b/base/src/META-INF/blaze-base.xml
@@ -97,8 +97,8 @@
     <projectService serviceImplementation="com.google.idea.blaze.base.console.BlazeConsoleView"/>
     <fileTypeFactory implementation="com.google.idea.blaze.base.plugin.BlazeFileTypeFactory" />
 
-    <projectConfigurable instance="com.google.idea.blaze.base.settings.ui.BlazeUserSettingsConfigurable"
-                         id ="blaze.view" displayName="Blaze Settings"/>
+    <applicationConfigurable instance="com.google.idea.blaze.base.settings.ui.BlazeUserSettingsConfigurable"
+                         id ="blaze.view" />
 
     <projectService serviceInterface="com.google.idea.blaze.base.sync.data.BlazeProjectDataManager"
                     serviceImplementation="com.google.idea.blaze.base.sync.data.BlazeProjectDataManagerImpl"/>
@@ -113,14 +113,12 @@
                         serviceImplementation="com.google.idea.blaze.base.io.InputStreamProviderImpl"/>
     <applicationService serviceInterface="com.google.idea.blaze.base.io.FileAttributeProvider"
                         serviceImplementation="com.google.idea.blaze.base.io.FileAttributeProvider"/>
-    <applicationService serviceInterface="com.google.idea.blaze.base.io.WorkspaceScanner"
-                        serviceImplementation="com.google.idea.blaze.base.io.VfsWorkspaceScanner"/>
     <applicationService serviceInterface="com.google.idea.blaze.base.buildmodifier.BuildFileModifier"
                         serviceImplementation="com.google.idea.blaze.base.lang.buildfile.actions.BuildFileModifierImpl"/>
     <projectService serviceInterface="com.google.idea.blaze.base.buildmodifier.FileSystemModifier"
                     serviceImplementation="com.google.idea.blaze.base.buildmodifier.FileSystemModifierImpl"/>
-    <applicationService serviceInterface="com.google.idea.blaze.base.run.rulefinder.RuleFinder"
-                        serviceImplementation="com.google.idea.blaze.base.run.rulefinder.RuleFinderImpl"/>
+    <applicationService serviceInterface="com.google.idea.blaze.base.run.targetfinder.TargetFinder"
+                        serviceImplementation="com.google.idea.blaze.base.run.targetfinder.TargetFinderImpl"/>
     <applicationService serviceInterface="com.google.idea.blaze.base.command.info.BlazeInfo"
                         serviceImplementation="com.google.idea.blaze.base.command.info.BlazeInfoImpl"/>
 
@@ -132,13 +130,13 @@
                     serviceImplementation="com.google.idea.blaze.base.projectview.ProjectViewManagerImpl"/>
     <applicationService serviceInterface="com.google.idea.blaze.base.sync.aspects.BlazeIdeInterface"
                         serviceImplementation="com.google.idea.blaze.base.sync.aspects.BlazeIdeInterfaceAspectsImpl"/>
-    <projectService serviceInterface="com.google.idea.blaze.base.run.TestRuleFinder"
-                        serviceImplementation="com.google.idea.blaze.base.run.testmap.TestRuleFinderImpl"/>
+    <projectService serviceInterface="com.google.idea.blaze.base.run.TestTargetFinder"
+                        serviceImplementation="com.google.idea.blaze.base.run.testmap.TestTargetFilterImpl"/>
     <projectService serviceInterface="com.google.idea.blaze.base.console.BlazeConsoleService"
                     serviceImplementation="com.google.idea.blaze.base.console.BlazeConsoleServiceImpl"/>
     <projectService serviceImplementation="com.google.idea.blaze.base.buildmap.FileToBuildMap"/>
-    <projectService serviceInterface="com.google.idea.blaze.base.rulemaps.SourceToRuleMap"
-                    serviceImplementation="com.google.idea.blaze.base.rulemaps.SourceToRuleMapImpl"/>
+    <projectService serviceInterface="com.google.idea.blaze.base.targetmaps.SourceToTargetMap"
+                    serviceImplementation="com.google.idea.blaze.base.targetmaps.SourceToTargetMapImpl"/>
     <projectService serviceImplementation="com.google.idea.blaze.base.settings.BlazeImportSettingsManager"/>
     <projectService serviceImplementation="com.google.idea.blaze.base.settings.BlazeImportSettingsManagerLegacy"/>
     <applicationService serviceImplementation="com.google.idea.blaze.base.settings.BlazeUserSettings"/>
@@ -161,6 +159,8 @@
     <stepsBeforeRunProvider implementation="com.google.idea.blaze.base.run.BlazeBeforeRunTaskProvider"/>
     <applicationService serviceInterface="com.google.idea.blaze.base.help.BlazeHelpHandler"
                         serviceImplementation="com.google.idea.blaze.base.help.BlazeHelpHandlerImpl"/>
+
+    <additionalTextAttributes scheme="Default" file="base/resources/colorSchemes/BuildDefault.xml"/>
   </extensions>
 
   <extensions defaultExtensionNs="com.intellij">
@@ -256,15 +256,16 @@
     <extensionPoint qualifiedName="com.google.idea.blaze.BlazeUserSettingsContributor" interface="com.google.idea.blaze.base.settings.ui.BlazeUserSettingsContributor$Provider"/>
     <extensionPoint qualifiedName="com.google.idea.blaze.BlazePsiDirectoryRootNodeNameModifier" interface="com.google.idea.blaze.base.treeview.BlazePsiDirectoryRootNodeNameModifier"/>
     <extensionPoint qualifiedName="com.google.idea.blaze.FileCache" interface="com.google.idea.blaze.base.filecache.FileCache"/>
-    <extensionPoint qualifiedName="com.google.idea.blaze.TestRuleHeuristic" interface="com.google.idea.blaze.base.run.TestRuleHeuristic"/>
+    <extensionPoint qualifiedName="com.google.idea.blaze.TestTargetHeuristic" interface="com.google.idea.blaze.base.run.TestTargetHeuristic"/>
     <extensionPoint qualifiedName="com.google.idea.blaze.ProjectDataDirectoryValidator" interface="com.google.idea.blaze.base.wizard2.ProjectDataDirectoryValidator"/>
+    <extensionPoint qualifiedName="com.google.idea.blaze.AspectStrategyProvider" interface="com.google.idea.blaze.base.sync.aspects.strategy.AspectStrategyProvider"/>
   </extensionPoints>
 
   <extensions defaultExtensionNs="com.google.idea.blaze">
     <SyncListener implementation="com.google.idea.blaze.base.run.BlazeRunConfigurationSyncListener"/>
     <SyncListener implementation="com.google.idea.blaze.base.sync.status.BlazeSyncStatusListener"/>
-    <SyncListener implementation="com.google.idea.blaze.base.run.testmap.TestRuleFinderImpl$ClearTestMap"/>
-    <SyncListener implementation="com.google.idea.blaze.base.rulemaps.SourceToRuleMapImpl$ClearSourceToTargetMap"/>
+    <SyncListener implementation="com.google.idea.blaze.base.run.testmap.TestTargetFilterImpl$ClearTestMap"/>
+    <SyncListener implementation="com.google.idea.blaze.base.targetmaps.SourceToTargetMapImpl$ClearSourceToTargetMap"/>
     <SyncListener implementation="com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpecProviderImpl"/>
     <SyncPlugin implementation="com.google.idea.blaze.base.lang.buildfile.sync.BuildLangSyncPlugin"/>
     <BlazeWizardOptionProvider implementation="com.google.idea.blaze.base.wizard2.BazelWizardOptionProvider"/>
@@ -274,9 +275,10 @@
     <BuildSystemProvider implementation="com.google.idea.blaze.base.bazel.BazelBuildSystemProvider" order="last"/>
     <BuildifierBinaryProvider implementation="com.google.idea.blaze.base.buildmodifier.BazelBuildifierBinaryProvider"/>
     <BlazeCommandRunConfigurationHandlerProvider implementation="com.google.idea.blaze.base.run.confighandler.BlazeCommandGenericRunConfigurationHandlerProvider" order="last"/>
-    <TestRuleHeuristic implementation="com.google.idea.blaze.base.run.RuleNameHeuristic" order="first"/>
-    <TestRuleHeuristic implementation="com.google.idea.blaze.base.run.TestSizeHeuristic" order="last" id="TestSizeHeuristic"/>
+    <TestTargetHeuristic implementation="com.google.idea.blaze.base.run.TargetNameHeuristic" order="first"/>
+    <TestTargetHeuristic implementation="com.google.idea.blaze.base.run.TestSizeHeuristic" order="last" id="TestSizeHeuristic"/>
     <RunConfigurationFactory implementation="com.google.idea.blaze.base.run.BlazeBuildTargetRunConfigurationFactory" order="last"/>
+    <AspectStrategyProvider implementation="com.google.idea.blaze.base.sync.aspects.strategy.AspectStrategyProviderBazel" order="last"/>
   </extensions>
 
 </idea-plugin>
diff --git a/base/src/com/google/idea/blaze/base/actions/BlazeCompileFileAction.java b/base/src/com/google/idea/blaze/base/actions/BlazeCompileFileAction.java
index 9c9ed3b..00ad470 100644
--- a/base/src/com/google/idea/blaze/base/actions/BlazeCompileFileAction.java
+++ b/base/src/com/google/idea/blaze/base/actions/BlazeCompileFileAction.java
@@ -28,7 +28,6 @@
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.projectview.ProjectViewManager;
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.rulemaps.SourceToRuleMap;
 import com.google.idea.blaze.base.scope.BlazeContext;
 import com.google.idea.blaze.base.scope.ScopedTask;
 import com.google.idea.blaze.base.scope.scopes.BlazeConsoleScope;
@@ -40,6 +39,7 @@
 import com.google.idea.blaze.base.sync.aspects.BlazeIdeInterface;
 import com.google.idea.blaze.base.sync.aspects.BlazeIdeInterface.BuildResult;
 import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.base.targetmaps.SourceToTargetMap;
 import com.google.idea.blaze.base.util.SaveUtil;
 import com.intellij.openapi.actionSystem.AnActionEvent;
 import com.intellij.openapi.actionSystem.CommonDataKeys;
@@ -82,7 +82,7 @@
     Project project = e.getProject();
     VirtualFile virtualFile = e.getData(CommonDataKeys.VIRTUAL_FILE);
     if (project != null && virtualFile != null) {
-      return SourceToRuleMap.getInstance(project)
+      return SourceToTargetMap.getInstance(project)
           .getTargetsToBuildForSourceFile(new File(virtualFile.getPath()));
     }
     return ImmutableList.of();
diff --git a/base/src/com/google/idea/blaze/base/buildmap/FileToBuildMap.java b/base/src/com/google/idea/blaze/base/buildmap/FileToBuildMap.java
index b810547..dea0dd5 100644
--- a/base/src/com/google/idea/blaze/base/buildmap/FileToBuildMap.java
+++ b/base/src/com/google/idea/blaze/base/buildmap/FileToBuildMap.java
@@ -17,8 +17,8 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.rulemaps.SourceToRuleMap;
 import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.base.targetmaps.SourceToTargetMap;
 import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.project.Project;
 import java.io.File;
@@ -44,10 +44,10 @@
     if (blazeProjectData == null) {
       return ImmutableList.of();
     }
-    return SourceToRuleMap.getInstance(project)
+    return SourceToTargetMap.getInstance(project)
         .getRulesForSourceFile(file)
         .stream()
-        .map(blazeProjectData.ruleMap::get)
+        .map(blazeProjectData.targetMap::get)
         .filter(Objects::nonNull)
         .map((ruleIdeInfo) -> ruleIdeInfo.buildFile)
         .filter(Objects::nonNull)
diff --git a/base/src/com/google/idea/blaze/base/command/BlazeFlags.java b/base/src/com/google/idea/blaze/base/command/BlazeFlags.java
index 484924b..2338f24 100644
--- a/base/src/com/google/idea/blaze/base/command/BlazeFlags.java
+++ b/base/src/com/google/idea/blaze/base/command/BlazeFlags.java
@@ -36,7 +36,7 @@
   //   --test_strategy=exclusive --test_timeout=9999 --nocache_test_results
   public static final String JAVA_TEST_DEBUG = "--java_debug";
   // Tells the Java wrapper stub to launch JVM in remote debugging mode, waiting for a connection
-  public static final String JAVA_BINARY_DEBUG = "--debug";
+  public static final String JAVA_BINARY_DEBUG = "--wrapper_script_flag=--debug";
   // Runs tests locally, in sequence (rather than parallel), and streams their results to stdout.
   public static final String TEST_OUTPUT_STREAMED = "--test_output=streamed";
   // Filters the unit tests that are run (used with regexp for Java/Robolectric tests).
diff --git a/base/src/com/google/idea/blaze/base/command/BuildFlagsProviderImpl.java b/base/src/com/google/idea/blaze/base/command/BuildFlagsProviderImpl.java
index eb4b510..290f001 100644
--- a/base/src/com/google/idea/blaze/base/command/BuildFlagsProviderImpl.java
+++ b/base/src/com/google/idea/blaze/base/command/BuildFlagsProviderImpl.java
@@ -26,18 +26,18 @@
 /** Flags added to blaze/bazel build commands. */
 public class BuildFlagsProviderImpl implements BuildFlagsProvider {
 
-  private static final BoolExperiment EXPERIMENT_USE_VERSION_WINDOW_FOR_DIRTY_NODE_GC =
+  private static final BoolExperiment experimentUseVersionWindowForDirtyNodeGc =
       new BoolExperiment("ide_build_info.use_version_window_for_dirty_node_gc", false);
-  private static final BoolExperiment EXPERIMENT_NO_EXPERIMENTAL_CHECK_OUTPUT_FILES =
+  private static final BoolExperiment experimentNoExperimentalCheckOutputFiles =
       new BoolExperiment("build.noexperimental_check_output_files", false);
 
   @Override
   public void addBuildFlags(
       BuildSystem buildSystem, ProjectViewSet projectViewSet, List<String> flags) {
-    if (EXPERIMENT_USE_VERSION_WINDOW_FOR_DIRTY_NODE_GC.getValue()) {
+    if (experimentUseVersionWindowForDirtyNodeGc.getValue()) {
       flags.add(VERSION_WINDOW_FOR_DIRTY_NODE_GC);
     }
-    if (EXPERIMENT_NO_EXPERIMENTAL_CHECK_OUTPUT_FILES.getValue()) {
+    if (experimentNoExperimentalCheckOutputFiles.getValue()) {
       flags.add(NO_CHECK_OUTPUTS);
     }
     flags.add("--curses=no");
diff --git a/base/src/com/google/idea/blaze/base/command/ExperimentalShowArtifactsLineProcessor.java b/base/src/com/google/idea/blaze/base/command/ExperimentalShowArtifactsLineProcessor.java
index 22c8c6a..8d512c0 100644
--- a/base/src/com/google/idea/blaze/base/command/ExperimentalShowArtifactsLineProcessor.java
+++ b/base/src/com/google/idea/blaze/base/command/ExperimentalShowArtifactsLineProcessor.java
@@ -18,6 +18,7 @@
 import com.google.idea.blaze.base.async.process.LineProcessingOutputStream;
 import java.io.File;
 import java.util.List;
+import java.util.function.Predicate;
 import org.jetbrains.annotations.NotNull;
 
 /** Collects the output of --experimental_show_artifacts */
@@ -26,13 +27,17 @@
   private static final String OUTPUT_START = "Build artifacts:";
   private static final String OUTPUT_MARKER = ">>>";
 
-  final List<File> fileList;
-  private final String fileType;
+  private final List<File> fileList;
+  private final Predicate<String> filter;
   boolean insideBuildResult = false;
 
-  public ExperimentalShowArtifactsLineProcessor(List<File> fileList, String fileType) {
+  public ExperimentalShowArtifactsLineProcessor(List<File> fileList) {
+    this(fileList, (value) -> true);
+  }
+
+  public ExperimentalShowArtifactsLineProcessor(List<File> fileList, Predicate<String> filter) {
     this.fileList = fileList;
-    this.fileType = fileType;
+    this.filter = filter;
   }
 
   @Override
@@ -46,7 +51,7 @@
       insideBuildResult = line.startsWith(OUTPUT_MARKER);
       if (insideBuildResult) {
         String fileName = line.substring(OUTPUT_MARKER.length());
-        if (fileName.endsWith(fileType)) {
+        if (filter.test(fileName)) {
           fileList.add(new File(fileName));
         }
       }
diff --git a/base/src/com/google/idea/blaze/base/command/info/BlazeInfo.java b/base/src/com/google/idea/blaze/base/command/info/BlazeInfo.java
index 89530b7..5346e8c 100644
--- a/base/src/com/google/idea/blaze/base/command/info/BlazeInfo.java
+++ b/base/src/com/google/idea/blaze/base/command/info/BlazeInfo.java
@@ -29,6 +29,8 @@
   public static final String EXECUTION_ROOT_KEY = "execution_root";
   public static final String PACKAGE_PATH_KEY = "package_path";
   public static final String BUILD_LANGUAGE = "build-language";
+  public static final String OUTPUT_BASE_KEY = "output_base";
+  public static final String MASTER_LOG = "master-log";
 
   public static String blazeBinKey(BuildSystem buildSystem) {
     switch (buildSystem) {
diff --git a/base/src/com/google/idea/blaze/base/ide/NewBlazePackageDialog.java b/base/src/com/google/idea/blaze/base/ide/NewBlazePackageDialog.java
index 14e02d4..f51ccd9 100644
--- a/base/src/com/google/idea/blaze/base/ide/NewBlazePackageDialog.java
+++ b/base/src/com/google/idea/blaze/base/ide/NewBlazePackageDialog.java
@@ -18,7 +18,7 @@
 import com.google.common.collect.Lists;
 import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.model.primitives.RuleName;
+import com.google.idea.blaze.base.model.primitives.TargetName;
 import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.ui.BlazeValidationError;
@@ -111,8 +111,8 @@
     File newPackageDirectory = new File(parentDirectoryFile, newPackageName);
     WorkspacePath newPackagePath = workspaceRoot.workspacePathFor(newPackageDirectory);
 
-    RuleName newRuleName = newRuleUI.getRuleName();
-    Label newRule = new Label(newPackagePath, newRuleName);
+    TargetName newTargetName = newRuleUI.getRuleName();
+    Label newRule = new Label(newPackagePath, newTargetName);
     Kind ruleKind = newRuleUI.getSelectedRuleKind();
     try {
       parentDirectory.checkCreateSubdirectory(newPackageName);
diff --git a/base/src/com/google/idea/blaze/base/ide/NewBlazeRuleDialog.java b/base/src/com/google/idea/blaze/base/ide/NewBlazeRuleDialog.java
index 64dcd3d..ef114da 100644
--- a/base/src/com/google/idea/blaze/base/ide/NewBlazeRuleDialog.java
+++ b/base/src/com/google/idea/blaze/base/ide/NewBlazeRuleDialog.java
@@ -18,7 +18,7 @@
 import com.google.idea.blaze.base.buildmodifier.BuildFileModifier;
 import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.model.primitives.RuleName;
+import com.google.idea.blaze.base.model.primitives.TargetName;
 import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.scope.BlazeContext;
@@ -88,13 +88,13 @@
 
   @Override
   protected void doOKAction() {
-    RuleName ruleName = newRuleUI.getRuleName();
+    TargetName targetName = newRuleUI.getRuleName();
     Kind ruleKind = newRuleUI.getSelectedRuleKind();
 
     WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
     WorkspacePath workspacePath =
         workspaceRoot.workspacePathFor(new File(buildFile.getParent().getPath()));
-    Label newRule = new Label(workspacePath, ruleName);
+    Label newRule = new Label(workspacePath, targetName);
     BuildFileModifier buildFileModifier = BuildFileModifier.getInstance();
     boolean success = buildFileModifier.addRule(project, context, newRule, ruleKind);
 
diff --git a/base/src/com/google/idea/blaze/base/ide/NewRuleUI.java b/base/src/com/google/idea/blaze/base/ide/NewRuleUI.java
index f602bc2..8f859fd 100644
--- a/base/src/com/google/idea/blaze/base/ide/NewRuleUI.java
+++ b/base/src/com/google/idea/blaze/base/ide/NewRuleUI.java
@@ -17,7 +17,7 @@
 
 import com.google.common.collect.Lists;
 import com.google.idea.blaze.base.model.primitives.Kind;
-import com.google.idea.blaze.base.model.primitives.RuleName;
+import com.google.idea.blaze.base.model.primitives.TargetName;
 import com.google.idea.blaze.base.ui.BlazeValidationError;
 import com.google.idea.blaze.base.ui.UiUtil;
 import com.intellij.ide.IdeBundle;
@@ -57,8 +57,8 @@
   }
 
   @NotNull
-  public RuleName getRuleName() {
-    return RuleName.create(ruleNameField.getText());
+  public TargetName getRuleName() {
+    return TargetName.create(ruleNameField.getText());
   }
 
   @Nullable
@@ -80,6 +80,6 @@
       return false;
     }
 
-    return RuleName.validate(inputString, errors);
+    return TargetName.validate(inputString, errors);
   }
 }
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/AndroidRuleIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/AndroidIdeInfo.java
similarity index 95%
rename from base/src/com/google/idea/blaze/base/ideinfo/AndroidRuleIdeInfo.java
rename to base/src/com/google/idea/blaze/base/ideinfo/AndroidIdeInfo.java
index 31d7d60..760fac7 100644
--- a/base/src/com/google/idea/blaze/base/ideinfo/AndroidRuleIdeInfo.java
+++ b/base/src/com/google/idea/blaze/base/ideinfo/AndroidIdeInfo.java
@@ -22,7 +22,7 @@
 import org.jetbrains.annotations.Nullable;
 
 /** Ide info specific to android rules. */
-public final class AndroidRuleIdeInfo implements Serializable {
+public final class AndroidIdeInfo implements Serializable {
   private static final long serialVersionUID = 5L;
 
   public final Collection<ArtifactLocation> resources;
@@ -34,7 +34,7 @@
   public boolean generateResourceClass;
   @Nullable public Label legacyResources;
 
-  public AndroidRuleIdeInfo(
+  public AndroidIdeInfo(
       Collection<ArtifactLocation> resources,
       @Nullable String resourceJavaPackage,
       boolean generateResourceClass,
@@ -108,7 +108,7 @@
       return this;
     }
 
-    public AndroidRuleIdeInfo build() {
+    public AndroidIdeInfo build() {
       if (!resources.isEmpty() || manifest != null) {
         if (!generateResourceClass) {
           throw new IllegalStateException(
@@ -116,7 +116,7 @@
         }
       }
 
-      return new AndroidRuleIdeInfo(
+      return new AndroidIdeInfo(
           resources,
           resourceJavaPackage,
           generateResourceClass,
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/ArtifactLocation.java b/base/src/com/google/idea/blaze/base/ideinfo/ArtifactLocation.java
index 463f4ae..a23ab9c 100644
--- a/base/src/com/google/idea/blaze/base/ideinfo/ArtifactLocation.java
+++ b/base/src/com/google/idea/blaze/base/ideinfo/ArtifactLocation.java
@@ -22,17 +22,19 @@
 
 /** Represents a blaze-produced artifact. */
 public final class ArtifactLocation implements Serializable, Comparable<ArtifactLocation> {
-  private static final long serialVersionUID = 3L;
+  private static final long serialVersionUID = 4L;
 
   public final String rootExecutionPathFragment;
   public final String relativePath;
   public final boolean isSource;
+  public final boolean isExternal;
 
   private ArtifactLocation(
-      String rootExecutionPathFragment, String relativePath, boolean isSource) {
+      String rootExecutionPathFragment, String relativePath, boolean isSource, boolean isExternal) {
     this.rootExecutionPathFragment = rootExecutionPathFragment;
     this.relativePath = relativePath;
     this.isSource = isSource;
+    this.isExternal = isExternal;
   }
 
   /** Gets the path relative to the root path. */
@@ -65,6 +67,7 @@
     String relativePath;
     String rootExecutionPathFragment = "";
     boolean isSource;
+    boolean isExternal;
 
     public Builder setRelativePath(String relativePath) {
       this.relativePath = relativePath;
@@ -81,8 +84,13 @@
       return this;
     }
 
+    public Builder setIsExternal(boolean isExternal) {
+      this.isExternal = isExternal;
+      return this;
+    }
+
     public ArtifactLocation build() {
-      return new ArtifactLocation(rootExecutionPathFragment, relativePath, isSource);
+      return new ArtifactLocation(rootExecutionPathFragment, relativePath, isSource, isExternal);
     }
   }
 
@@ -97,12 +105,13 @@
     ArtifactLocation that = (ArtifactLocation) o;
     return Objects.equal(rootExecutionPathFragment, that.rootExecutionPathFragment)
         && Objects.equal(relativePath, that.relativePath)
-        && Objects.equal(isSource, that.isSource);
+        && Objects.equal(isSource, that.isSource)
+        && Objects.equal(isExternal, that.isExternal);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hashCode(rootExecutionPathFragment, relativePath, isSource);
+    return Objects.hashCode(rootExecutionPathFragment, relativePath, isSource, isExternal);
   }
 
   @Override
@@ -116,6 +125,7 @@
         .compare(rootExecutionPathFragment, o.rootExecutionPathFragment)
         .compare(relativePath, o.relativePath)
         .compare(isSource, o.isSource)
+        .compare(isExternal, o.isExternal)
         .result();
   }
 }
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/CRuleIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/CIdeInfo.java
similarity index 95%
rename from base/src/com/google/idea/blaze/base/ideinfo/CRuleIdeInfo.java
rename to base/src/com/google/idea/blaze/base/ideinfo/CIdeInfo.java
index 954ab66..de80f8c 100644
--- a/base/src/com/google/idea/blaze/base/ideinfo/CRuleIdeInfo.java
+++ b/base/src/com/google/idea/blaze/base/ideinfo/CIdeInfo.java
@@ -19,8 +19,8 @@
 import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
 import java.io.Serializable;
 
-/** Sister class to {@link JavaRuleIdeInfo} */
-public class CRuleIdeInfo implements Serializable {
+/** Sister class to {@link JavaIdeInfo} */
+public class CIdeInfo implements Serializable {
   private static final long serialVersionUID = 6L;
 
   public final ImmutableList<ArtifactLocation> sources;
@@ -32,7 +32,7 @@
   public final ImmutableList<String> transitiveDefines;
   public final ImmutableList<ExecutionRootPath> transitiveSystemIncludeDirectories;
 
-  public CRuleIdeInfo(
+  public CIdeInfo(
       ImmutableList<ArtifactLocation> sources,
       ImmutableList<ExecutionRootPath> transitiveIncludeDirectories,
       ImmutableList<ExecutionRootPath> transitiveQuoteIncludeDirectories,
@@ -89,8 +89,8 @@
       return this;
     }
 
-    public CRuleIdeInfo build() {
-      return new CRuleIdeInfo(
+    public CIdeInfo build() {
+      return new CIdeInfo(
           sources.build(),
           transitiveIncludeDirectories.build(),
           transitiveQuoteIncludeDirectories.build(),
@@ -101,7 +101,7 @@
 
   @Override
   public String toString() {
-    return "CRuleIdeInfo{"
+    return "CIdeInfo{"
         + "\n"
         + "  sources="
         + sources
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/CToolchainIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/CToolchainIdeInfo.java
index 867b1fe..f1c0174 100644
--- a/base/src/com/google/idea/blaze/base/ideinfo/CToolchainIdeInfo.java
+++ b/base/src/com/google/idea/blaze/base/ideinfo/CToolchainIdeInfo.java
@@ -20,7 +20,7 @@
 import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
 import java.io.Serializable;
 
-/** Sister class to {@link JavaRuleIdeInfo} */
+/** Sister class to {@link JavaIdeInfo} */
 public class CToolchainIdeInfo implements Serializable {
   private static final long serialVersionUID = 3L;
 
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/Dependency.java b/base/src/com/google/idea/blaze/base/ideinfo/Dependency.java
new file mode 100644
index 0000000..5709c98
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ideinfo/Dependency.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.ideinfo;
+
+import com.google.common.base.Objects;
+import java.io.Serializable;
+
+/** Represents a dependency between two targets. */
+public class Dependency implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  /** Type of dependency */
+  public enum DependencyType {
+    COMPILE_TIME,
+    RUNTIME
+  }
+
+  public final TargetKey targetKey;
+  public final DependencyType dependencyType;
+
+  public Dependency(TargetKey targetKey, DependencyType dependencyType) {
+    this.targetKey = targetKey;
+    this.dependencyType = dependencyType;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    Dependency that = (Dependency) o;
+    return Objects.equal(targetKey, that.targetKey) && dependencyType == that.dependencyType;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(targetKey, dependencyType);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/JavaRuleIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/JavaIdeInfo.java
similarity index 92%
rename from base/src/com/google/idea/blaze/base/ideinfo/JavaRuleIdeInfo.java
rename to base/src/com/google/idea/blaze/base/ideinfo/JavaIdeInfo.java
index 35f5a15..bf9e527 100644
--- a/base/src/com/google/idea/blaze/base/ideinfo/JavaRuleIdeInfo.java
+++ b/base/src/com/google/idea/blaze/base/ideinfo/JavaIdeInfo.java
@@ -21,7 +21,7 @@
 import org.jetbrains.annotations.Nullable;
 
 /** Ide info specific to java rules. */
-public final class JavaRuleIdeInfo implements Serializable {
+public final class JavaIdeInfo implements Serializable {
   private static final long serialVersionUID = 2L;
 
   /**
@@ -46,7 +46,7 @@
   /** File containing dependencies. */
   @Nullable public final ArtifactLocation jdepsFile;
 
-  public JavaRuleIdeInfo(
+  public JavaIdeInfo(
       Collection<LibraryArtifact> jars,
       Collection<LibraryArtifact> generatedJars,
       @Nullable LibraryArtifact filteredGenJar,
@@ -84,8 +84,8 @@
       return this;
     }
 
-    public JavaRuleIdeInfo build() {
-      return new JavaRuleIdeInfo(jars.build(), generatedJars.build(), filteredGenJar, null, null);
+    public JavaIdeInfo build() {
+      return new JavaIdeInfo(jars.build(), generatedJars.build(), filteredGenJar, null, null);
     }
   }
 }
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/PyIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/PyIdeInfo.java
new file mode 100644
index 0000000..32305b0
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ideinfo/PyIdeInfo.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.ideinfo;
+
+import com.google.common.collect.ImmutableList;
+import java.io.Serializable;
+
+/** Ide info specific to python rules. */
+public class PyIdeInfo implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  public final ImmutableList<ArtifactLocation> sources;
+
+  public PyIdeInfo(ImmutableList<ArtifactLocation> sources) {
+    this.sources = sources;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /** Builder for python rule info */
+  public static class Builder {
+    private final ImmutableList.Builder<ArtifactLocation> sources = ImmutableList.builder();
+
+    public Builder addSources(Iterable<ArtifactLocation> sources) {
+      this.sources.addAll(sources);
+      return this;
+    }
+
+    public PyIdeInfo build() {
+      return new PyIdeInfo(sources.build());
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "PyIdeInfo{" + "\n" + "  sources=" + sources + "\n" + '}';
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/RuleKey.java b/base/src/com/google/idea/blaze/base/ideinfo/RuleKey.java
deleted file mode 100644
index 8d4cc16..0000000
--- a/base/src/com/google/idea/blaze/base/ideinfo/RuleKey.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2016 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.idea.blaze.base.ideinfo;
-
-import com.google.common.base.Objects;
-import com.google.idea.blaze.base.model.primitives.Label;
-import java.io.Serializable;
-import java.util.Comparator;
-
-/** A key that uniquely idenfifies a rule in the rule map */
-public class RuleKey implements Serializable, Comparable<RuleKey> {
-  private static final long serialVersionUID = 1L;
-  public static final Comparator<RuleKey> COMPARATOR =
-      (o1, o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.label.toString(), o2.label.toString());
-
-  public final Label label;
-
-  private RuleKey(Label label) {
-    this.label = label;
-  }
-
-  /** Returns a key identifying dep for a dependency rule -> dep */
-  public static RuleKey forDependency(RuleIdeInfo rule, Label dep) {
-    return new RuleKey(dep);
-  }
-
-  /** Returns a key identifying a plain target */
-  public static RuleKey forPlainTarget(Label label) {
-    return new RuleKey(label);
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    RuleKey key = (RuleKey) o;
-    return Objects.equal(label, key.label);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hashCode(label);
-  }
-
-  @Override
-  public String toString() {
-    return label.toString();
-  }
-
-  @Override
-  public int compareTo(RuleKey o) {
-    return COMPARATOR.compare(this, o);
-  }
-}
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/Tags.java b/base/src/com/google/idea/blaze/base/ideinfo/Tags.java
index 6f07e33..c3834db 100644
--- a/base/src/com/google/idea/blaze/base/ideinfo/Tags.java
+++ b/base/src/com/google/idea/blaze/base/ideinfo/Tags.java
@@ -18,16 +18,16 @@
 /** Tag constants used by our rules. */
 public class Tags {
   /** Forces import of the target output. */
-  public static final String RULE_TAG_IMPORT_TARGET_OUTPUT = "intellij-import-target-output";
+  public static final String TARGET_TAG_IMPORT_TARGET_OUTPUT = "intellij-import-target-output";
 
-  public static final String RULE_TAG_IMPORT_AS_LIBRARY_LEGACY = "aswb-import-as-library";
+  public static final String TARGET_TAG_IMPORT_AS_LIBRARY_LEGACY = "aswb-import-as-library";
 
   /**
    * Signals to the import process that the output of this rule will be provided by the IntelliJ
    * SDK.
    */
-  public static final String RULE_TAG_PROVIDED_BY_SDK = "intellij-provided-by-sdk";
+  public static final String TARGET_TAG_PROVIDED_BY_SDK = "intellij-provided-by-sdk";
 
   /** Ignores the target. */
-  public static final String RULE_TAG_EXCLUDE_TARGET = "intellij-exclude-target";
+  public static final String TARGET_TAG_EXCLUDE_TARGET = "intellij-exclude-target";
 }
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/RuleIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/TargetIdeInfo.java
similarity index 71%
rename from base/src/com/google/idea/blaze/base/ideinfo/RuleIdeInfo.java
rename to base/src/com/google/idea/blaze/base/ideinfo/TargetIdeInfo.java
index 34bf3ef..6ea3685 100644
--- a/base/src/com/google/idea/blaze/base/ideinfo/RuleIdeInfo.java
+++ b/base/src/com/google/idea/blaze/base/ideinfo/TargetIdeInfo.java
@@ -16,6 +16,7 @@
 package com.google.idea.blaze.base.ideinfo;
 
 import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.ideinfo.Dependency.DependencyType;
 import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.model.primitives.Label;
 import java.io.Serializable;
@@ -24,53 +25,51 @@
 import java.util.List;
 import javax.annotation.Nullable;
 
-/** Simple implementation of RuleIdeInfo. */
-public final class RuleIdeInfo implements Serializable {
-  private static final long serialVersionUID = 12L;
+/** Simple implementation of TargetIdeInfo. */
+public final class TargetIdeInfo implements Serializable {
+  private static final long serialVersionUID = 14L;
 
-  public final RuleKey key;
-  public final Label label;
+  public final TargetKey key;
   public final Kind kind;
   @Nullable public final ArtifactLocation buildFile;
-  public final Collection<Label> dependencies;
-  public final Collection<Label> runtimeDeps;
+  public final Collection<Dependency> dependencies;
   public final Collection<String> tags;
   public final Collection<ArtifactLocation> sources;
-  @Nullable public final CRuleIdeInfo cRuleIdeInfo;
+  @Nullable public final CIdeInfo cIdeInfo;
   @Nullable public final CToolchainIdeInfo cToolchainIdeInfo;
-  @Nullable public final JavaRuleIdeInfo javaRuleIdeInfo;
-  @Nullable public final AndroidRuleIdeInfo androidRuleIdeInfo;
+  @Nullable public final JavaIdeInfo javaIdeInfo;
+  @Nullable public final AndroidIdeInfo androidIdeInfo;
+  @Nullable public final PyIdeInfo pyIdeInfo;
   @Nullable public final TestIdeInfo testIdeInfo;
   @Nullable public final ProtoLibraryLegacyInfo protoLibraryLegacyInfo;
   @Nullable public final JavaToolchainIdeInfo javaToolchainIdeInfo;
 
-  public RuleIdeInfo(
-      Label label,
+  public TargetIdeInfo(
+      TargetKey key,
       Kind kind,
       @Nullable ArtifactLocation buildFile,
-      Collection<Label> dependencies,
-      Collection<Label> runtimeDeps,
+      Collection<Dependency> dependencies,
       Collection<String> tags,
       Collection<ArtifactLocation> sources,
-      @Nullable CRuleIdeInfo cRuleIdeInfo,
+      @Nullable CIdeInfo cIdeInfo,
       @Nullable CToolchainIdeInfo cToolchainIdeInfo,
-      @Nullable JavaRuleIdeInfo javaRuleIdeInfo,
-      @Nullable AndroidRuleIdeInfo androidRuleIdeInfo,
+      @Nullable JavaIdeInfo javaIdeInfo,
+      @Nullable AndroidIdeInfo androidIdeInfo,
+      @Nullable PyIdeInfo pyIdeInfo,
       @Nullable TestIdeInfo testIdeInfo,
       @Nullable ProtoLibraryLegacyInfo protoLibraryLegacyInfo,
       @Nullable JavaToolchainIdeInfo javaToolchainIdeInfo) {
-    this.key = RuleKey.forPlainTarget(label);
-    this.label = label;
+    this.key = key;
     this.kind = kind;
     this.buildFile = buildFile;
     this.dependencies = dependencies;
-    this.runtimeDeps = runtimeDeps;
     this.tags = tags;
     this.sources = sources;
-    this.cRuleIdeInfo = cRuleIdeInfo;
+    this.cIdeInfo = cIdeInfo;
     this.cToolchainIdeInfo = cToolchainIdeInfo;
-    this.javaRuleIdeInfo = javaRuleIdeInfo;
-    this.androidRuleIdeInfo = androidRuleIdeInfo;
+    this.javaIdeInfo = javaIdeInfo;
+    this.androidIdeInfo = androidIdeInfo;
+    this.pyIdeInfo = pyIdeInfo;
     this.testIdeInfo = testIdeInfo;
     this.protoLibraryLegacyInfo = protoLibraryLegacyInfo;
     this.javaToolchainIdeInfo = javaToolchainIdeInfo;
@@ -78,7 +77,7 @@
 
   @Override
   public String toString() {
-    return label.toString();
+    return key.toString();
   }
 
   /** Returns whether this rule is one of the kinds. */
@@ -95,7 +94,7 @@
   }
 
   public boolean isPlainTarget() {
-    return true;
+    return key.isPlainTarget();
   }
 
   public static Builder builder() {
@@ -104,18 +103,17 @@
 
   /** Builder for rule ide info */
   public static class Builder {
-    private Label label;
+    private TargetKey key;
     private Kind kind;
     private ArtifactLocation buildFile;
-    private final List<Label> dependencies = Lists.newArrayList();
-    private final List<Label> runtimeDeps = Lists.newArrayList();
+    private final List<Dependency> dependencies = Lists.newArrayList();
     private final List<String> tags = Lists.newArrayList();
     private final List<ArtifactLocation> sources = Lists.newArrayList();
-    private final List<LibraryArtifact> libraries = Lists.newArrayList();
-    private CRuleIdeInfo cRuleIdeInfo;
+    private CIdeInfo cIdeInfo;
     private CToolchainIdeInfo cToolchainIdeInfo;
-    private JavaRuleIdeInfo javaRuleIdeInfo;
-    private AndroidRuleIdeInfo androidRuleIdeInfo;
+    private JavaIdeInfo javaIdeInfo;
+    private AndroidIdeInfo androidIdeInfo;
+    private PyIdeInfo pyIdeInfo;
     private TestIdeInfo testIdeInfo;
     private ProtoLibraryLegacyInfo protoLibraryLegacyInfo;
     private JavaToolchainIdeInfo javaToolchainIdeInfo;
@@ -125,7 +123,7 @@
     }
 
     public Builder setLabel(Label label) {
-      this.label = label;
+      this.key = TargetKey.forPlainTarget(label);
       return this;
     }
 
@@ -152,17 +150,17 @@
       return addSource(source.build());
     }
 
-    public Builder setJavaInfo(JavaRuleIdeInfo.Builder builder) {
-      javaRuleIdeInfo = builder.build();
+    public Builder setJavaInfo(JavaIdeInfo.Builder builder) {
+      javaIdeInfo = builder.build();
       return this;
     }
 
-    public Builder setCInfo(CRuleIdeInfo cInfo) {
-      this.cRuleIdeInfo = cInfo;
+    public Builder setCInfo(CIdeInfo cInfo) {
+      this.cIdeInfo = cInfo;
       return this;
     }
 
-    public Builder setCInfo(CRuleIdeInfo.Builder cInfo) {
+    public Builder setCInfo(CIdeInfo.Builder cInfo) {
       return setCInfo(cInfo.build());
     }
 
@@ -175,15 +173,20 @@
       return setCToolchainInfo(info.build());
     }
 
-    public Builder setAndroidInfo(AndroidRuleIdeInfo androidInfo) {
-      this.androidRuleIdeInfo = androidInfo;
+    public Builder setAndroidInfo(AndroidIdeInfo androidInfo) {
+      this.androidIdeInfo = androidInfo;
       return this;
     }
 
-    public Builder setAndroidInfo(AndroidRuleIdeInfo.Builder androidInfo) {
+    public Builder setAndroidInfo(AndroidIdeInfo.Builder androidInfo) {
       return setAndroidInfo(androidInfo.build());
     }
 
+    public Builder setPyInfo(PyIdeInfo.Builder pyInfo) {
+      this.pyIdeInfo = pyInfo.build();
+      return this;
+    }
+
     public Builder setTestInfo(TestIdeInfo.Builder testInfo) {
       this.testIdeInfo = testInfo.build();
       return this;
@@ -210,7 +213,8 @@
     }
 
     public Builder addDependency(Label label) {
-      this.dependencies.add(label);
+      this.dependencies.add(
+          new Dependency(TargetKey.forPlainTarget(label), DependencyType.COMPILE_TIME));
       return this;
     }
 
@@ -219,23 +223,24 @@
     }
 
     public Builder addRuntimeDep(Label label) {
-      this.runtimeDeps.add(label);
+      this.dependencies.add(
+          new Dependency(TargetKey.forPlainTarget(label), DependencyType.RUNTIME));
       return this;
     }
 
-    public RuleIdeInfo build() {
-      return new RuleIdeInfo(
-          label,
+    public TargetIdeInfo build() {
+      return new TargetIdeInfo(
+          key,
           kind,
           buildFile,
           dependencies,
-          runtimeDeps,
           tags,
           sources,
-          cRuleIdeInfo,
+          cIdeInfo,
           cToolchainIdeInfo,
-          javaRuleIdeInfo,
-          androidRuleIdeInfo,
+          javaIdeInfo,
+          androidIdeInfo,
+          pyIdeInfo,
           testIdeInfo,
           protoLibraryLegacyInfo,
           javaToolchainIdeInfo);
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/TargetKey.java b/base/src/com/google/idea/blaze/base/ideinfo/TargetKey.java
new file mode 100644
index 0000000..0a347c6
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/ideinfo/TargetKey.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.ideinfo;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ComparisonChain;
+import com.google.idea.blaze.base.model.primitives.Label;
+import java.io.Serializable;
+import javax.annotation.Nullable;
+
+/** A key that uniquely idenfifies a target in the target map */
+public class TargetKey implements Serializable, Comparable<TargetKey> {
+  private static final long serialVersionUID = 2L;
+
+  public final Label label;
+  @Nullable private final String aspectId;
+
+  private TargetKey(Label label, @Nullable String aspectId) {
+    this.label = label;
+    this.aspectId = aspectId;
+  }
+
+  /** Returns a key identifying a plain target */
+  public static TargetKey forPlainTarget(Label label) {
+    return new TargetKey(label, null);
+  }
+
+  /** Returns a key identifying a general target */
+  public static TargetKey forGeneralTarget(Label label, @Nullable String aspectId) {
+    return new TargetKey(label, aspectId);
+  }
+
+  public boolean isPlainTarget() {
+    return aspectId == null;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    TargetKey key = (TargetKey) o;
+    return Objects.equal(label, key.label) && Objects.equal(aspectId, key.aspectId);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(label, aspectId);
+  }
+
+  @Override
+  public String toString() {
+    if (aspectId == null) {
+      return label.toString();
+    }
+    return label.toString() + "#" + aspectId;
+  }
+
+  @Override
+  public int compareTo(TargetKey o) {
+    return ComparisonChain.start().compare(label, o.label).compare(aspectId, o.aspectId).result();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/RuleMap.java b/base/src/com/google/idea/blaze/base/ideinfo/TargetMap.java
similarity index 63%
rename from base/src/com/google/idea/blaze/base/ideinfo/RuleMap.java
rename to base/src/com/google/idea/blaze/base/ideinfo/TargetMap.java
index 365eaf4..36edb24 100644
--- a/base/src/com/google/idea/blaze/base/ideinfo/RuleMap.java
+++ b/base/src/com/google/idea/blaze/base/ideinfo/TargetMap.java
@@ -20,28 +20,28 @@
 import java.io.Serializable;
 
 /** Map of configured targets (and soon aspects). */
-public class RuleMap implements Serializable {
+public class TargetMap implements Serializable {
   private static final long serialVersionUID = 2L;
 
-  private final ImmutableMap<RuleKey, RuleIdeInfo> ruleMap;
+  private final ImmutableMap<TargetKey, TargetIdeInfo> targetMap;
 
-  public RuleMap(ImmutableMap<RuleKey, RuleIdeInfo> ruleMap) {
-    this.ruleMap = ruleMap;
+  public TargetMap(ImmutableMap<TargetKey, TargetIdeInfo> targetMap) {
+    this.targetMap = targetMap;
   }
 
-  public RuleIdeInfo get(RuleKey key) {
-    return ruleMap.get(key);
+  public TargetIdeInfo get(TargetKey key) {
+    return targetMap.get(key);
   }
 
-  public boolean contains(RuleKey key) {
-    return ruleMap.containsKey(key);
+  public boolean contains(TargetKey key) {
+    return targetMap.containsKey(key);
   }
 
-  public ImmutableCollection<RuleIdeInfo> rules() {
-    return ruleMap.values();
+  public ImmutableCollection<TargetIdeInfo> targets() {
+    return targetMap.values();
   }
 
-  public ImmutableMap<RuleKey, RuleIdeInfo> map() {
-    return ruleMap;
+  public ImmutableMap<TargetKey, TargetIdeInfo> map() {
+    return targetMap;
   }
 }
diff --git a/base/src/com/google/idea/blaze/base/ideinfo/TestIdeInfo.java b/base/src/com/google/idea/blaze/base/ideinfo/TestIdeInfo.java
index 9257344..f36b7e6 100644
--- a/base/src/com/google/idea/blaze/base/ideinfo/TestIdeInfo.java
+++ b/base/src/com/google/idea/blaze/base/ideinfo/TestIdeInfo.java
@@ -43,8 +43,8 @@
   }
 
   @Nullable
-  public static TestSize getTestSize(RuleIdeInfo rule) {
-    TestIdeInfo testIdeInfo = rule.testIdeInfo;
+  public static TestSize getTestSize(TargetIdeInfo target) {
+    TestIdeInfo testIdeInfo = target.testIdeInfo;
     if (testIdeInfo == null) {
       return null;
     }
diff --git a/base/src/com/google/idea/blaze/base/io/VfsWorkspaceScanner.java b/base/src/com/google/idea/blaze/base/io/VfsWorkspaceScanner.java
deleted file mode 100644
index de12e98..0000000
--- a/base/src/com/google/idea/blaze/base/io/VfsWorkspaceScanner.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2016 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.idea.blaze.base.io;
-
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.openapi.vfs.VirtualFile;
-
-/** Checks the workspace using the VFS. */
-class VfsWorkspaceScanner implements WorkspaceScanner {
-  private final LocalFileSystem localFileSystem;
-
-  public VfsWorkspaceScanner() {
-    this.localFileSystem = LocalFileSystem.getInstance();
-  }
-
-  @Override
-  public boolean exists(WorkspaceRoot workspaceRoot, WorkspacePath workspacePath) {
-    VirtualFile virtualFile =
-        localFileSystem.refreshAndFindFileByPath(
-            workspaceRoot.fileForPath(workspacePath).getPath());
-    return virtualFile != null && virtualFile.exists();
-  }
-}
diff --git a/base/src/com/google/idea/blaze/base/io/WorkspaceScanner.java b/base/src/com/google/idea/blaze/base/io/WorkspaceScanner.java
deleted file mode 100644
index 4fdc691..0000000
--- a/base/src/com/google/idea/blaze/base/io/WorkspaceScanner.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2016 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.idea.blaze.base.io;
-
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.intellij.openapi.components.ServiceManager;
-
-/** Used to scan the file system */
-public interface WorkspaceScanner {
-  static WorkspaceScanner getInstance() {
-    return ServiceManager.getService(WorkspaceScanner.class);
-  }
-
-  boolean exists(WorkspaceRoot workspaceRoot, WorkspacePath workspacePath);
-}
diff --git a/base/src/com/google/idea/blaze/base/issueparser/BlazeIssueParser.java b/base/src/com/google/idea/blaze/base/issueparser/BlazeIssueParser.java
index 57fcbbd..ef4337a 100644
--- a/base/src/com/google/idea/blaze/base/issueparser/BlazeIssueParser.java
+++ b/base/src/com/google/idea/blaze/base/issueparser/BlazeIssueParser.java
@@ -135,6 +135,7 @@
       return IssueOutput.issue(type, matcher.group(5))
           .inFile(file)
           .onLine(Integer.parseInt(matcher.group(2)))
+          .inColumn(parseOptionalInt(matcher.group(4)))
           .build();
     }
   }
@@ -170,6 +171,7 @@
             IssueOutput.error(message.toString())
                 .inFile(new File(matcher.group(2)))
                 .onLine(Integer.parseInt(matcher.group(3)))
+                .inColumn(parseOptionalInt(matcher.group(4)))
                 .build());
       }
     }
@@ -185,10 +187,20 @@
       return IssueOutput.error(matcher.group(5))
           .inFile(new File(matcher.group(2)))
           .onLine(Integer.parseInt(matcher.group(3)))
+          .inColumn(parseOptionalInt(matcher.group(4)))
           .build();
     }
   }
 
+  /** Falls back to returning -1 if no integer can be parsed. */
+  private static int parseOptionalInt(String intString) {
+    try {
+      return Integer.parseInt(intString);
+    } catch (NumberFormatException e) {
+      return -1;
+    }
+  }
+
   static class LinelessBuildParser extends SingleLineParser {
     LinelessBuildParser() {
       super("(ERROR): (.*?):char offsets [0-9]+--[0-9]+: (.*)");
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/actions/BuildFileModifierImpl.java b/base/src/com/google/idea/blaze/base/lang/buildfile/actions/BuildFileModifierImpl.java
index 561bf04..a4fe33a 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/actions/BuildFileModifierImpl.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/actions/BuildFileModifierImpl.java
@@ -56,7 +56,7 @@
                 LOG.error("No BUILD file found at location: " + newRule.blazePackage());
                 return false;
               }
-              buildFile.add(createRule(project, ruleKind, newRule.ruleName().toString()));
+              buildFile.add(createRule(project, ruleKind, newRule.targetName().toString()));
               return true;
             });
   }
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/completion/CompletionResultsProcessor.java b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/CompletionResultsProcessor.java
index ba6364d..c5a32e5 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/completion/CompletionResultsProcessor.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/CompletionResultsProcessor.java
@@ -17,7 +17,7 @@
 
 import com.google.common.collect.Maps;
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildElement;
-import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
+import com.google.idea.blaze.base.lang.buildfile.psi.LoadedSymbol;
 import com.google.idea.blaze.base.lang.buildfile.references.QuoteType;
 import com.intellij.codeInsight.lookup.LookupElement;
 import com.intellij.psi.PsiElement;
@@ -43,11 +43,10 @@
     if (buildElement == originalElement) {
       return true;
     }
-    if (buildElement instanceof StringLiteral) {
-      StringLiteral literal = (StringLiteral) buildElement;
-      results.put(
-          literal.getStringContents(),
-          new StringLiteralReferenceLookupElement((StringLiteral) buildElement, quoteType));
+    if (buildElement instanceof LoadedSymbol) {
+      LoadedSymbol loadedSymbol = (LoadedSymbol) buildElement;
+      String string = loadedSymbol.getSymbolString();
+      results.put(string, new LoadedSymbolReferenceLookupElement(loadedSymbol, string, quoteType));
     } else if (buildElement instanceof PsiNamedElement) {
       PsiNamedElement namedElement = (PsiNamedElement) buildElement;
       results.put(
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/completion/StringLiteralReferenceLookupElement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/LoadedSymbolReferenceLookupElement.java
similarity index 73%
rename from base/src/com/google/idea/blaze/base/lang/buildfile/completion/StringLiteralReferenceLookupElement.java
rename to base/src/com/google/idea/blaze/base/lang/buildfile/completion/LoadedSymbolReferenceLookupElement.java
index af85c1c..1a1bd43 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/completion/StringLiteralReferenceLookupElement.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/completion/LoadedSymbolReferenceLookupElement.java
@@ -15,7 +15,7 @@
  */
 package com.google.idea.blaze.base.lang.buildfile.completion;
 
-import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
+import com.google.idea.blaze.base.lang.buildfile.psi.LoadedSymbol;
 import com.google.idea.blaze.base.lang.buildfile.references.QuoteType;
 import com.intellij.openapi.util.NullableLazyValue;
 import com.intellij.psi.PsiElement;
@@ -26,21 +26,22 @@
  * We calculate the referenced element lazily, as it often won't be needed (e.g. when the string
  * doesn't match the string fragment being completed.
  */
-public class StringLiteralReferenceLookupElement extends BuildLookupElement {
+public class LoadedSymbolReferenceLookupElement extends BuildLookupElement {
 
-  private final StringLiteral literal;
-  private NullableLazyValue<PsiElement> referencedElement =
+  private final LoadedSymbol loadedSymbol;
+  private final NullableLazyValue<PsiElement> referencedElement =
       new NullableLazyValue<PsiElement>() {
         @Nullable
         @Override
         protected PsiElement compute() {
-          return literal.getReferencedElement();
+          return loadedSymbol.getVisibleElement();
         }
       };
 
-  public StringLiteralReferenceLookupElement(StringLiteral literal, QuoteType quoteType) {
-    super(literal.getStringContents(), quoteType);
-    this.literal = literal;
+  public LoadedSymbolReferenceLookupElement(
+      LoadedSymbol loadedSymbol, String symbolString, QuoteType quoteType) {
+    super(symbolString, quoteType);
+    this.loadedSymbol = loadedSymbol;
   }
 
   @Nullable
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildTargetElementEvaluator.java b/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildTargetElementEvaluator.java
index bc96e3d..7ef9399 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildTargetElementEvaluator.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/findusages/BuildTargetElementEvaluator.java
@@ -30,10 +30,10 @@
 import javax.annotation.Nullable;
 
 /**
- * StringLiterals can reference multiple targets (e.g. "package:target" references both the package
- * and the target). IntelliJ defaults to highlighting / navigating to the innermost reference, but
- * in this case, we want the opposite behavior (the target reference should trump the package
- * reference).
+ * StringLiterals can reference multiple targets (e.g. "//package:target" references both the
+ * package and the target). IntelliJ defaults to highlighting / navigating to the innermost
+ * reference, but in this case, we want the opposite behavior (the target reference should trump the
+ * package reference).
  */
 public class BuildTargetElementEvaluator extends TargetElementEvaluatorEx2 {
 
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildColorsPage.java b/base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildColorsPage.java
index b348256..2c34759 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildColorsPage.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildColorsPage.java
@@ -27,7 +27,6 @@
 import com.intellij.openapi.options.colors.ColorSettingsPage;
 import com.intellij.psi.codeStyle.DisplayPriority;
 import com.intellij.psi.codeStyle.DisplayPrioritySortable;
-import com.intellij.util.PlatformUtils;
 import java.util.Map;
 import javax.swing.Icon;
 
@@ -49,6 +48,7 @@
         new AttributesDescriptor("Function definition", BuildSyntaxHighlighter.BUILD_FN_DEFINITION),
         new AttributesDescriptor("Parameter", BuildSyntaxHighlighter.BUILD_PARAMETER),
         new AttributesDescriptor("Keyword argument", BuildSyntaxHighlighter.BUILD_KEYWORD_ARG),
+        new AttributesDescriptor("Built-in name", BuildSyntaxHighlighter.BUILD_BUILTIN_NAME),
       };
 
   private static final Map<String, TextAttributesKey> ourTagToDescriptorMap =
@@ -58,6 +58,8 @@
           .put("kwarg", BuildSyntaxHighlighter.BUILD_KEYWORD_ARG)
           .put("comma", BuildSyntaxHighlighter.BUILD_COMMA)
           .put("number", BuildSyntaxHighlighter.BUILD_NUMBER)
+          .put("keyword", BuildSyntaxHighlighter.BUILD_KEYWORD)
+          .put("builtin", BuildSyntaxHighlighter.BUILD_BUILTIN_NAME)
           .build();
 
   @Override
@@ -92,9 +94,10 @@
   public String getDemoText() {
     return "def <funcDef>function</funcDef>(<param>x</param>, <kwarg>whatever</kwarg>=1):\n"
         + "    s = (\"Test\", 2+3, {'a': 'b'}, <param>x</param>)   # Comment\n"
-        + "    print s[0].lower()\n"
+        + "    <builtin>print</builtin> s[0].lower()\n"
+        + "    <keyword>return</keyword> <builtin>True</builtin>"
         + "\n"
-        + "java_library(\n"
+        + "<builtin>java_library</builtin>(\n"
         + "    <kwarg>name</kwarg> = \"lib\",\n"
         + "    <kwarg>srcs</kwarg> = glob([\"**/*.java\"]),\n"
         + ")\n";
@@ -107,8 +110,6 @@
 
   @Override
   public DisplayPriority getPriority() {
-    return PlatformUtils.isPyCharm()
-        ? DisplayPriority.KEY_LANGUAGE_SETTINGS
-        : DisplayPriority.LANGUAGE_SETTINGS;
+    return DisplayPriority.LANGUAGE_SETTINGS;
   }
 }
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildSyntaxHighlighter.java b/base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildSyntaxHighlighter.java
index b14e89b..093d648 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildSyntaxHighlighter.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/highlighting/BuildSyntaxHighlighter.java
@@ -26,6 +26,7 @@
 import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.OPERATION_SIGN;
 import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.PARAMETER;
 import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.PARENTHESES;
+import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.PREDEFINED_SYMBOL;
 import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.SEMICOLON;
 import static com.intellij.openapi.editor.DefaultLanguageHighlighterColors.STRING;
 
@@ -62,6 +63,8 @@
   public static final TextAttributesKey BUILD_KEYWORD_ARG = key("BUILD.KEYWORD.ARG", PARAMETER);
   public static final TextAttributesKey BUILD_FN_DEFINITION =
       key("BUILD.FN.DEFINITION", FUNCTION_DECLARATION);
+  public static final TextAttributesKey BUILD_BUILTIN_NAME =
+      TextAttributesKey.createTextAttributesKey("BUILD.BUILTIN_NAME", PREDEFINED_SYMBOL);
 
   private static TextAttributesKey key(String name, TextAttributesKey fallbackKey) {
     return TextAttributesKey.createTextAttributesKey(name, fallbackKey);
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuiltInNamesProvider.java b/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuiltInNamesProvider.java
new file mode 100644
index 0000000..7f12f08
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/language/semantics/BuiltInNamesProvider.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.lang.buildfile.language.semantics;
+
+import com.google.common.collect.ImmutableSet;
+import com.intellij.openapi.project.Project;
+
+/**
+ * The built-in names available in the BUILD language. This is not a complete list, and is only
+ * intended to be used for syntax highlighting.
+ */
+public class BuiltInNamesProvider {
+
+  // https://www.bazel.io/versions/master/docs/skylark/lib/globals.html
+  private static final ImmutableSet<String> GLOBALS =
+      ImmutableSet.of(
+          "Actions",
+          "DATA_CFG",
+          "False",
+          "HOST_CFG",
+          "None",
+          "PACKAGE_NAME",
+          "REPOSITORY_NAME",
+          "True",
+          "all",
+          "any",
+          "aspect",
+          "bool",
+          "dict",
+          "dir",
+          "enumerate",
+          "fail",
+          "getattr",
+          "hasattr",
+          "hash",
+          "int",
+          "len",
+          "list",
+          "max",
+          "min",
+          "print",
+          "provider",
+          "range",
+          "repository_rule",
+          "repr",
+          "reversed",
+          "rule",
+          "select",
+          "set",
+          "sorted",
+          "str",
+          "struct",
+          "type",
+          "zip");
+
+  public static ImmutableSet<String> getBuiltInNames(Project project) {
+    BuildLanguageSpec spec = BuildLanguageSpecProvider.getInstance().getLanguageSpec(project);
+    if (spec == null) {
+      return GLOBALS;
+    }
+    return ImmutableSet.<String>builder().addAll(GLOBALS).addAll(spec.getKnownRuleNames()).build();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/lexer/TokenKind.java b/base/src/com/google/idea/blaze/base/lang/buildfile/lexer/TokenKind.java
index c33754f..5b4517c 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/lexer/TokenKind.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/lexer/TokenKind.java
@@ -37,7 +37,6 @@
   EQUALS("="),
   EQUALS_EQUALS("=="),
   EXCEPT("except"),
-  FALSE("False"),
   FINALLY("finally"),
   FOR("for"),
   FROM("from"),
@@ -86,7 +85,6 @@
   STAR("*"),
   STAR_STAR("**"),
   STRING("string"),
-  TRUE("True"),
   TRY("try"),
   WHILE("while"),
   WITH("with"),
@@ -110,13 +108,13 @@
     return name;
   }
 
-  public static ImmutableSet<TokenKind> KEYWORDS =
+  public static final ImmutableSet<TokenKind> KEYWORDS =
       ImmutableSet.of(
-          AND, AS, ASSERT, BREAK, CLASS, CONTINUE, DEF, DEL, ELIF, ELSE, EXCEPT, FALSE, FINALLY,
-          FOR, FROM, GLOBAL, IF, IMPORT, IN, IS, LAMBDA, LOAD, NONLOCAL, NOT, OR, PASS, RAISE,
-          RETURN, TRUE, TRY, WHILE, WITH, YIELD);
+          AND, AS, ASSERT, BREAK, CLASS, CONTINUE, DEF, DEL, ELIF, ELSE, EXCEPT, FINALLY, FOR, FROM,
+          GLOBAL, IF, IMPORT, IN, IS, LAMBDA, LOAD, NONLOCAL, NOT, OR, PASS, RAISE, RETURN, TRY,
+          WHILE, WITH, YIELD);
 
-  public static ImmutableSet<TokenKind> OPERATIONS =
+  public static final ImmutableSet<TokenKind> OPERATIONS =
       ImmutableSet.of(
           AND,
           EQUALS_EQUALS,
@@ -135,6 +133,6 @@
           PIPE,
           STAR);
 
-  public static ImmutableSet<TokenKind> AUGMENTED_ASSIGNMENT_OPS =
+  public static final ImmutableSet<TokenKind> AUGMENTED_ASSIGNMENT_OPS =
       ImmutableSet.of(PLUS_EQUALS, MINUS_EQUALS, STAR_EQUALS, SLASH_EQUALS, PERCENT_EQUALS);
 }
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/parser/BuildParserDefinition.java b/base/src/com/google/idea/blaze/base/lang/buildfile/parser/BuildParserDefinition.java
index c195cc1..8aa9ab3 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/parser/BuildParserDefinition.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/parser/BuildParserDefinition.java
@@ -42,7 +42,7 @@
 /** Defines the BUILD file parser */
 public class BuildParserDefinition implements ParserDefinition {
 
-  private static final DeveloperFlag DEBUG = new DeveloperFlag("build.file.debug.mode");
+  private static final DeveloperFlag debug = new DeveloperFlag("build.file.debug.mode");
 
   @Override
   public Lexer createLexer(Project project) {
@@ -104,7 +104,7 @@
   private static class BuildParser implements PsiParser {
     @Override
     public ASTNode parse(IElementType root, PsiBuilder builder) {
-      if (DEBUG.getValue()) {
+      if (debug.getValue()) {
         System.err.println(builder.getUserDataUnprotected(FileContextUtil.CONTAINING_FILE_KEY));
       }
       PsiBuilder.Marker rootMarker = builder.mark();
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/parser/ExpressionParsing.java b/base/src/com/google/idea/blaze/base/lang/buildfile/parser/ExpressionParsing.java
index 9c20fd4..11688ef 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/parser/ExpressionParsing.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/parser/ExpressionParsing.java
@@ -21,16 +21,12 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildElementType;
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildElementTypes;
 import com.intellij.lang.PsiBuilder;
-import com.intellij.openapi.diagnostic.Logger;
 import java.util.EnumSet;
 import java.util.List;
 
 /** For parsing expressions in BUILD files. */
 public class ExpressionParsing extends Parsing {
 
-  private static final Logger LOG =
-      Logger.getInstance("com.google.idea.blaze.base.lang.buildfile.parser.ExpressionParsing");
-
   private static final ImmutableSet<TokenKind> LIST_TERMINATOR_SET =
       ImmutableSet.of(TokenKind.EOF, TokenKind.RBRACKET, TokenKind.SEMI);
 
@@ -47,7 +43,7 @@
           TokenKind.RPAREN,
           TokenKind.SEMI);
 
-  private static final ImmutableSet<TokenKind> EXPR_TERMINATOR_SET =
+  protected static final ImmutableSet<TokenKind> EXPR_TERMINATOR_SET =
       ImmutableSet.of(
           TokenKind.EOF,
           TokenKind.COLON,
@@ -60,25 +56,6 @@
           TokenKind.RPAREN,
           TokenKind.SLASH);
 
-  private static final ImmutableSet<TokenKind> BINARY_OPERATORS =
-      ImmutableSet.of(
-          TokenKind.AND,
-          TokenKind.EQUALS_EQUALS,
-          TokenKind.GREATER,
-          TokenKind.GREATER_EQUALS,
-          TokenKind.IN,
-          TokenKind.LESS,
-          TokenKind.LESS_EQUALS,
-          TokenKind.MINUS,
-          TokenKind.NOT_EQUALS,
-          TokenKind.NOT_IN,
-          TokenKind.OR,
-          TokenKind.PERCENT,
-          TokenKind.SLASH,
-          TokenKind.PLUS,
-          TokenKind.PIPE,
-          TokenKind.STAR);
-
   private static final ImmutableSet<TokenKind> FUNCALL_TERMINATOR_SET =
       ImmutableSet.of(TokenKind.EOF, TokenKind.RPAREN, TokenKind.SEMI, TokenKind.NEWLINE);
 
@@ -283,10 +260,6 @@
           marker.drop();
         }
         return;
-      case TRUE: // intentional fall-through -- both treated as vanilla identifiers
-      case FALSE:
-        buildTokenElement(BuildElementTypes.BOOLEAN_LITERAL);
-        return;
       case LBRACKET:
         parseListMaker();
         return;
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/parser/StatementParsing.java b/base/src/com/google/idea/blaze/base/lang/buildfile/parser/StatementParsing.java
index 90ad5ff..e22e0ba 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/parser/StatementParsing.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/parser/StatementParsing.java
@@ -20,21 +20,14 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildElementTypes;
 import com.intellij.lang.PsiBuilder;
 import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.psi.tree.IElementType;
 
 /** For parsing statements in BUILD files. */
 public class StatementParsing extends Parsing {
 
-  private static final Logger LOG =
-      Logger.getInstance("com.google.idea.blaze.base.lang.buildfile.parser.StatementParsing");
-
   private static final ImmutableSet<TokenKind> STATEMENT_TERMINATOR_SET =
       ImmutableSet.of(TokenKind.EOF, TokenKind.NEWLINE, TokenKind.SEMI);
 
-  private static final ImmutableSet<TokenKind> SMALL_STMT_START =
-      ImmutableSet.of(TokenKind.IDENTIFIER, TokenKind.RETURN);
-
   public StatementParsing(ParsingContext context) {
     super(context);
   }
@@ -107,7 +100,7 @@
       if (matches(TokenKind.RPAREN) || matchesAnyOf(STATEMENT_TERMINATOR_SET)) {
         break;
       }
-      hasSymbols |= parseStringLiteral(true);
+      hasSymbols |= parseLoadedSymbol();
     }
     if (!hasSymbols) {
       builder.error("'load' statements must include at least one loaded function");
@@ -115,6 +108,36 @@
     marker.done(BuildElementTypes.LOAD_STATEMENT);
   }
 
+  /** [IDENTIFIER '='] STRING */
+  private boolean parseLoadedSymbol() {
+    PsiBuilder.Marker marker = builder.mark();
+    if (currentToken() == TokenKind.STRING) {
+      parseStringLiteral(true);
+      marker.done(BuildElementTypes.LOADED_SYMBOL);
+      return true;
+    }
+    if (parseAlias()) {
+      marker.done(BuildElementTypes.LOADED_SYMBOL);
+      return true;
+    }
+    marker.drop();
+    builder.error("Expected a loaded symbol or alias");
+    syncPast(ExpressionParsing.EXPR_TERMINATOR_SET);
+    return false;
+  }
+
+  private boolean parseAlias() {
+    if (!atTokenSequence(TokenKind.IDENTIFIER, TokenKind.EQUALS, TokenKind.STRING)) {
+      return false;
+    }
+    PsiBuilder.Marker assignment = builder.mark();
+    buildTokenElement(BuildElementTypes.TARGET_EXPRESSION);
+    expect(TokenKind.EQUALS);
+    parseStringLiteral(true);
+    assignment.done(BuildElementTypes.ASSIGNMENT_STATEMENT);
+    return true;
+  }
+
   /** if_stmt ::= IF expr ':' suite (ELIF expr ':' suite)* [ELSE ':' suite] */
   private void parseIfStatement() {
     PsiBuilder.Marker marker = builder.mark();
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BooleanLiteral.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BooleanLiteral.java
deleted file mode 100644
index 112f400..0000000
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BooleanLiteral.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2016 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.idea.blaze.base.lang.buildfile.psi;
-
-import com.intellij.lang.ASTNode;
-
-/** PSI node for boolean literal expressions */
-public class BooleanLiteral extends BuildElementImpl implements LiteralExpression {
-
-  public BooleanLiteral(ASTNode astNode) {
-    super(astNode);
-  }
-
-  @Override
-  protected void acceptVisitor(BuildElementVisitor visitor) {
-    visitor.visitBooleanLiteral(this);
-  }
-}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementImpl.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementImpl.java
index baf786d..55f472d 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementImpl.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementImpl.java
@@ -66,7 +66,7 @@
     return children.length <= index ? null : children[index];
   }
 
-  protected BuildElement[] buildElementChildren() {
+  public BuildElement[] buildElementChildren() {
     return Arrays.stream(getNode().getChildren(null))
         .map(ASTNode::getPsi)
         .filter(psiElement -> psiElement instanceof BuildElement)
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementTypes.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementTypes.java
index e19ba6c..5f33a6d 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementTypes.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementTypes.java
@@ -71,7 +71,6 @@
   BuildElementType DOT_EXPRESSION = new BuildElementType("dot_expr", DotExpression.class);
   BuildElementType STRING_LITERAL = new BuildElementType("string", StringLiteral.class);
   BuildElementType INTEGER_LITERAL = new BuildElementType("int", IntegerLiteral.class);
-  BuildElementType BOOLEAN_LITERAL = new BuildElementType("bool", BooleanLiteral.class);
   BuildElementType LIST_LITERAL = new BuildElementType("list", ListLiteral.class);
   BuildElementType GLOB_EXPRESSION = new BuildElementType("glob", GlobExpression.class);
   BuildElementType REFERENCE_EXPRESSION =
@@ -79,6 +78,7 @@
   BuildElementType TARGET_EXPRESSION = new BuildElementType("target", TargetExpression.class);
   BuildElementType LIST_COMPREHENSION_EXPR =
       new BuildElementType("list_comp", ListComprehensionExpression.class);
+  BuildElementType LOADED_SYMBOL = new BuildElementType("loaded_symbol", LoadedSymbol.class);
 
   TokenSet EXPRESSIONS =
       TokenSet.create(
@@ -89,12 +89,12 @@
           DOT_EXPRESSION,
           STRING_LITERAL,
           INTEGER_LITERAL,
-          BOOLEAN_LITERAL,
           LIST_LITERAL,
           REFERENCE_EXPRESSION,
           TARGET_EXPRESSION,
           LIST_COMPREHENSION_EXPR,
-          GLOB_EXPRESSION);
+          GLOB_EXPRESSION,
+          LOADED_SYMBOL);
 
   TokenSet STATEMENTS =
       TokenSet.create(
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementVisitor.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementVisitor.java
index f62775b..68e9455 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementVisitor.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildElementVisitor.java
@@ -104,10 +104,6 @@
     visitElement(node);
   }
 
-  public void visitBooleanLiteral(BooleanLiteral node) {
-    visitElement(node);
-  }
-
   public void visitListLiteral(ListLiteral node) {
     visitElement(node);
   }
@@ -143,4 +139,8 @@
   public void visitPassStatement(PassStatement node) {
     visitElement(node);
   }
+
+  public void visitLoadedSymbol(LoadedSymbol node) {
+    visitElement(node);
+  }
 }
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildFile.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildFile.java
index 348f8a6..b9cc459 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildFile.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/BuildFile.java
@@ -202,9 +202,9 @@
   @Nullable
   public FunctionStatement findLoadedFunction(String name) {
     for (LoadStatement loadStatement : findChildrenByClass(LoadStatement.class)) {
-      for (StringLiteral importedFunctionNode : loadStatement.getImportedSymbolElements()) {
-        if (name.equals(importedFunctionNode.getStringContents())) {
-          PsiElement element = importedFunctionNode.getReferencedElement();
+      for (LoadedSymbol loadedSymbol : loadStatement.getImportedSymbolElements()) {
+        if (name.equals(loadedSymbol.getSymbolString())) {
+          PsiElement element = loadedSymbol.getLoadedElement();
           return element instanceof FunctionStatement ? (FunctionStatement) element : null;
         }
       }
@@ -216,8 +216,9 @@
     BuildElement[] resultHolder = new BuildElement[1];
     Processor<BuildElement> processor =
         buildElement -> {
-          if (buildElement instanceof StringLiteral) {
-            buildElement = BuildElement.asBuildElement(buildElement.getReferencedElement());
+          if (buildElement instanceof LoadedSymbol) {
+            buildElement =
+                BuildElement.asBuildElement(((LoadedSymbol) buildElement).getVisibleElement());
           }
           if (buildElement instanceof PsiNamedElement && name.equals(buildElement.getName())) {
             resultHolder[0] = buildElement;
@@ -257,7 +258,7 @@
         break;
       }
       if (child instanceof LoadStatement) {
-        for (StringLiteral importedSymbol : ((LoadStatement) child).getImportedSymbolElements()) {
+        for (LoadedSymbol importedSymbol : ((LoadStatement) child).getImportedSymbolElements()) {
           if (!processor.process(importedSymbol)) {
             return false;
           }
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/LoadStatement.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/LoadStatement.java
index cd92715..31dd222 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/LoadStatement.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/LoadStatement.java
@@ -50,43 +50,33 @@
     return firstString != null ? StringLiteral.stripQuotes(firstString.getText()) : null;
   }
 
+  /** The symbols as they appear in the loaded extension (i.e. ignoring aliases). */
+  public StringLiteral[] getLoadedSymbols() {
+    return Arrays.stream(getImportedSymbolElements())
+        .map(LoadedSymbol::getImport)
+        .toArray(StringLiteral[]::new);
+  }
+
   /** The string nodes referencing imported functions. */
   public FunctionStatement[] getImportedFunctionReferences() {
-    return Arrays.stream(getChildStrings())
-        .skip(1)
+    return Arrays.stream(getLoadedSymbols())
         .map(BuildElement::getReferencedElement)
         .filter(e -> e instanceof FunctionStatement)
         .toArray(FunctionStatement[]::new);
   }
 
-  /** The string nodes referencing imported functions. */
-  public StringLiteral[] getImportedSymbolElements() {
-    StringLiteral[] childStrings = getChildStrings();
-    return childStrings.length < 2
-        ? new StringLiteral[0]
-        : Arrays.copyOfRange(childStrings, 1, childStrings.length);
-  }
-
-  public String[] getImportedSymbolNames() {
-    return Arrays.stream(getImportedSymbolElements())
-        .map(StringLiteral::getStringContents)
-        .toArray(String[]::new);
-  }
-
-  @Nullable
-  public StringLiteral findImportedSymbolElement(String name) {
-    for (StringLiteral string : getImportedSymbolElements()) {
-      if (name.equals(string.getStringContents())) {
-        return string;
-      }
-    }
-    return null;
-  }
-
-  public StringLiteral[] getChildStrings() {
-    return Arrays.stream(getNode().getChildren(BuildElementTypes.STRINGS))
+  public LoadedSymbol[] getImportedSymbolElements() {
+    return Arrays.stream(getNode().getChildren(null))
         .map(ASTNode::getPsi)
-        .toArray(StringLiteral[]::new);
+        .filter(psiElement -> psiElement instanceof LoadedSymbol)
+        .toArray(LoadedSymbol[]::new);
+  }
+
+  /** Aliased symbol name, if alias is present. */
+  public String[] getVisibleSymbolNames() {
+    return Arrays.stream(getImportedSymbolElements())
+        .map(LoadedSymbol::getSymbolString)
+        .toArray(String[]::new);
   }
 
   @Override
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/LoadedSymbol.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/LoadedSymbol.java
new file mode 100644
index 0000000..5456e90
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/LoadedSymbol.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.lang.buildfile.psi;
+
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
+import javax.annotation.Nullable;
+
+/** PSI element for a loaded symbol within a load statement (either a StringLiteral or an alias). */
+public class LoadedSymbol extends BuildElementImpl implements Expression {
+
+  public LoadedSymbol(ASTNode astNode) {
+    super(astNode);
+  }
+
+  @Override
+  protected void acceptVisitor(BuildElementVisitor visitor) {
+    visitor.visitLoadedSymbol(this);
+  }
+
+  @Nullable
+  public String getSymbolString() {
+    PsiElement firstChild = getFirstChild();
+    if (firstChild instanceof StringLiteral) {
+      return ((StringLiteral) firstChild).getStringContents();
+    }
+    if (firstChild instanceof AssignmentStatement) {
+      return ((AssignmentStatement) firstChild).getName();
+    }
+    return null;
+  }
+
+  @Nullable
+  public StringLiteral getImport() {
+    return PsiUtils.findFirstChildOfClassRecursive(this, StringLiteral.class);
+  }
+
+  /** The PsiElement referenced in the loaded extension. */
+  @Nullable
+  public PsiElement getLoadedElement() {
+    StringLiteral literal = getImport();
+    return literal != null ? literal.getReferencedElement() : null;
+  }
+
+  /**
+   * If the symbol is aliased, stops there, otherwise continues to source. This is to support usage
+   * highlighting within a file.
+   */
+  @Nullable
+  public PsiElement getVisibleElement() {
+    PsiElement firstChild = getFirstChild();
+    if (firstChild instanceof StringLiteral) {
+      return ((StringLiteral) firstChild).getReferencedElement();
+    }
+    if (firstChild instanceof AssignmentStatement) {
+      return ((AssignmentStatement) firstChild).getLeftHandSideExpression();
+    }
+    return null;
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/StringLiteral.java b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/StringLiteral.java
index 9afb380..009dda6 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/psi/StringLiteral.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/psi/StringLiteral.java
@@ -119,9 +119,8 @@
   @Nullable
   @Override
   public PsiReference getReference() {
-    PsiElement parent = getParent();
-    if (parent instanceof LoadStatement) {
-      LoadStatement load = (LoadStatement) parent;
+    LoadStatement load = getLoadStatementParent();
+    if (load != null) {
       StringLiteral importNode = load.getImportPsiElement();
       if (importNode == null) {
         return null;
@@ -135,8 +134,18 @@
     return new LabelReference(this, true);
   }
 
-  public boolean insideLoadStatement() {
-    return getParentType() == BuildElementTypes.LOAD_STATEMENT;
+  @Nullable
+  public LoadStatement getLoadStatementParent() {
+    PsiElement parent = getParent();
+    if (parent instanceof LoadStatement) {
+      // the skylark extension label
+      return (LoadStatement) parent;
+    }
+    if (parent instanceof AssignmentStatement) {
+      // could be part of an aliased symbol
+      parent = parent.getParent();
+    }
+    return parent instanceof LoadedSymbol ? (LoadStatement) parent.getParent() : null;
   }
 
   @Override
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/references/BuildReferenceManager.java b/base/src/com/google/idea/blaze/base/lang/buildfile/references/BuildReferenceManager.java
index 6d728b8..6635ade 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/references/BuildReferenceManager.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/BuildReferenceManager.java
@@ -21,7 +21,7 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
 import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
 import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.model.primitives.RuleName;
+import com.google.idea.blaze.base.model.primitives.TargetName;
 import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.google.idea.blaze.base.settings.Blaze;
 import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
@@ -61,27 +61,31 @@
   /** Finds the PSI element associated with the given label. */
   @Nullable
   public PsiElement resolveLabel(Label label) {
-    return resolveLabel(label.blazePackage(), label.ruleName(), false);
+    return resolveLabel(label.blazePackage(), label.targetName(), false);
   }
 
   /** Finds the PSI element associated with the given label. */
   @Nullable
   public PsiElement resolveLabel(
-      WorkspacePath packagePath, RuleName ruleName, boolean excludeRules) {
+      WorkspacePath packagePath, TargetName targetName, boolean excludeRules) {
     File packageDir = resolvePackage(packagePath);
     if (packageDir == null) {
       return null;
     }
 
+    if (targetName.toString().equals("__pkg__")) {
+      return findBuildFile(packageDir);
+    }
+
     if (!excludeRules) {
-      FuncallExpression target = findRule(packageDir, ruleName);
+      FuncallExpression target = findRule(packageDir, targetName);
       if (target != null) {
         return target;
       }
     }
 
     // try a direct file reference (e.g. ":a.java")
-    File fullFile = new File(packageDir, ruleName.toString());
+    File fullFile = new File(packageDir, targetName.toString());
     if (FileAttributeProvider.getInstance().exists(fullFile)) {
       return resolveFile(fullFile);
     }
@@ -89,9 +93,9 @@
     return null;
   }
 
-  private FuncallExpression findRule(File packageDir, RuleName ruleName) {
+  private FuncallExpression findRule(File packageDir, TargetName targetName) {
     BuildFile psiFile = findBuildFile(packageDir);
-    return psiFile != null ? psiFile.findRule(ruleName.toString()) : null;
+    return psiFile != null ? psiFile.findRule(targetName.toString()) : null;
   }
 
   @Nullable
@@ -203,7 +207,7 @@
   }
 
   @Nullable
-  private BuildFile findBuildFile(@Nullable File packageDirectory) {
+  public BuildFile findBuildFile(@Nullable File packageDirectory) {
     FileAttributeProvider provider = FileAttributeProvider.getInstance();
     if (packageDirectory == null || !provider.isDirectory(packageDirectory)) {
       return null;
@@ -226,16 +230,16 @@
    */
   @Nullable
   public File resolveParentDirectory(@Nullable Label label) {
-    return label != null ? resolveParentDirectory(label.blazePackage(), label.ruleName()) : null;
+    return label != null ? resolveParentDirectory(label.blazePackage(), label.targetName()) : null;
   }
 
   @Nullable
-  private File resolveParentDirectory(WorkspacePath packagePath, RuleName ruleName) {
+  private File resolveParentDirectory(WorkspacePath packagePath, TargetName targetName) {
     File packageFile = resolvePackage(packagePath);
     if (packageFile == null) {
       return null;
     }
-    String rulePathParent = PathUtil.getParentPath(ruleName.toString());
+    String rulePathParent = PathUtil.getParentPath(targetName.toString());
     return new File(packageFile, rulePathParent);
   }
 
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/references/FuncallReference.java b/base/src/com/google/idea/blaze/base/lang/buildfile/references/FuncallReference.java
index 7f6cca8..526e2ec 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/references/FuncallReference.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/FuncallReference.java
@@ -17,11 +17,12 @@
 
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
 import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
-import com.google.idea.blaze.base.lang.buildfile.psi.FunctionStatement;
 import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.google.idea.blaze.base.lang.buildfile.search.ResolveUtil;
 import com.intellij.lang.ASTNode;
 import com.intellij.openapi.util.TextRange;
 import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiNamedElement;
 import com.intellij.psi.PsiReferenceBase;
 import com.intellij.util.IncorrectOperationException;
 import javax.annotation.Nullable;
@@ -35,9 +36,14 @@
 
   @Nullable
   @Override
-  public FunctionStatement resolve() {
+  public PsiNamedElement resolve() {
     String functionName = myElement.getFunctionName();
-    BuildFile file = (BuildFile) myElement.getContainingFile();
+    // first search in local scope (e.g. function passed in as an arg).
+    PsiNamedElement element = ResolveUtil.findInScope(myElement, functionName);
+    if (element != null) {
+      return element;
+    }
+    BuildFile file = myElement.getContainingFile();
     if (functionName == null || file == null) {
       return null;
     }
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelReference.java b/base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelReference.java
index 75c2aba..f1e6542 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelReference.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelReference.java
@@ -66,7 +66,7 @@
       return null;
     }
     if (!labelString.startsWith("//") && insideSkylarkExtension(myElement)) {
-      return getReferenceManager().resolveLabel(label.blazePackage(), label.ruleName(), true);
+      return getReferenceManager().resolveLabel(label.blazePackage(), label.targetName(), true);
     }
     return getReferenceManager().resolveLabel(label);
   }
@@ -211,7 +211,7 @@
     if (label == null) {
       return myElement;
     }
-    String ruleName = label.ruleName().toString();
+    String ruleName = label.targetName().toString();
     String newRuleName = newElementName;
 
     // handle subdirectories
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelUtils.java b/base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelUtils.java
index 251baed..3ac8a5f 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelUtils.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/references/LabelUtils.java
@@ -19,7 +19,7 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
 import com.google.idea.blaze.base.lang.buildfile.search.BlazePackage;
 import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.model.primitives.RuleName;
+import com.google.idea.blaze.base.model.primitives.TargetName;
 import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.codeInsight.completion.CompletionUtilCore;
 import com.intellij.util.PathUtil;
@@ -52,7 +52,7 @@
       return null;
     }
     WorkspacePath packagePath = blazePackage.buildFile.getPackageWorkspacePath();
-    RuleName name = RuleName.createIfValid(ruleName);
+    TargetName name = TargetName.createIfValid(ruleName);
     if (packagePath == null || name == null) {
       return null;
     }
@@ -148,7 +148,7 @@
     if (packagePath.isEmpty()) {
       return strings;
     }
-    String ruleName = label.ruleName().toString();
+    String ruleName = label.targetName().toString();
     if (PathUtil.getFileName(packagePath).equals(ruleName)) {
       strings.add("//" + packagePath); // implicit rule name equal to package name
     }
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/search/ResolveUtil.java b/base/src/com/google/idea/blaze/base/lang/buildfile/search/ResolveUtil.java
index e7854c5..d667970 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/search/ResolveUtil.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/search/ResolveUtil.java
@@ -21,10 +21,9 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.Expression;
 import com.google.idea.blaze.base.lang.buildfile.psi.ForStatement;
 import com.google.idea.blaze.base.lang.buildfile.psi.FunctionStatement;
-import com.google.idea.blaze.base.lang.buildfile.psi.LoadStatement;
+import com.google.idea.blaze.base.lang.buildfile.psi.LoadedSymbol;
 import com.google.idea.blaze.base.lang.buildfile.psi.Parameter;
 import com.google.idea.blaze.base.lang.buildfile.psi.StatementList;
-import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
 import com.google.idea.blaze.base.lang.buildfile.psi.TargetExpression;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
@@ -81,12 +80,12 @@
           if (buildElement instanceof PsiNamedElement && name.equals(buildElement.getName())) {
             resultHolder[0] = (PsiNamedElement) buildElement;
             return false;
-          } else if (buildElement instanceof StringLiteral) {
-            StringLiteral stringLiteral = (StringLiteral) buildElement;
-            if (name.equals(stringLiteral.getStringContents())) {
-              PsiElement referencedSymbol = stringLiteral.getReferencedElement();
-              if (referencedSymbol instanceof PsiNamedElement) {
-                resultHolder[0] = (PsiNamedElement) referencedSymbol;
+          } else if (buildElement instanceof LoadedSymbol) {
+            LoadedSymbol loadedSymbol = (LoadedSymbol) buildElement;
+            if (name.equals(loadedSymbol.getSymbolString())) {
+              PsiElement referencedElement = loadedSymbol.getVisibleElement();
+              if (referencedElement instanceof PsiNamedElement) {
+                resultHolder[0] = (PsiNamedElement) referencedElement;
                 return false;
               }
             }
@@ -124,18 +123,6 @@
     return resultHolder[0];
   }
 
-  /** @return false if processing was stopped */
-  public static boolean visitLoadedSymbols(BuildFile file, Processor<BuildElement> processor) {
-    for (LoadStatement loadStatement : file.findChildrenByClass(LoadStatement.class)) {
-      for (StringLiteral symbol : loadStatement.getImportedSymbolElements()) {
-        if (!processor.process(symbol)) {
-          return false;
-        }
-      }
-    }
-    return true;
-  }
-
   /**
    * Checks if the element we're searching for is represented by a file or directory.<br>
    * e.g. a java class PSI element, or an actual PsiFile element.
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/sync/BuildLangSyncPlugin.java b/base/src/com/google/idea/blaze/base/lang/buildfile/sync/BuildLangSyncPlugin.java
index 69b1b77..561cd1a 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/sync/BuildLangSyncPlugin.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/sync/BuildLangSyncPlugin.java
@@ -18,7 +18,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.idea.blaze.base.command.info.BlazeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpec;
 import com.google.idea.blaze.base.model.SyncState;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
@@ -57,7 +57,7 @@
       @Nullable WorkingSet workingSet,
       WorkspacePathResolver workspacePathResolver,
       ArtifactLocationDecoder artifactLocationDecoder,
-      RuleMap ruleMap,
+      TargetMap targetMap,
       SyncState.Builder syncStateBuilder,
       @Nullable SyncState previousSyncState) {
 
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/validation/ErrorAnnotator.java b/base/src/com/google/idea/blaze/base/lang/buildfile/validation/ErrorAnnotator.java
index 58e7476..40e89b2 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/validation/ErrorAnnotator.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/validation/ErrorAnnotator.java
@@ -16,14 +16,18 @@
 package com.google.idea.blaze.base.lang.buildfile.validation;
 
 import com.google.idea.blaze.base.lang.buildfile.psi.Argument;
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildElement;
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
 import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
 import com.google.idea.blaze.base.lang.buildfile.psi.FunctionStatement;
 import com.google.idea.blaze.base.lang.buildfile.psi.LoadStatement;
+import com.google.idea.blaze.base.lang.buildfile.psi.LoadedSymbol;
 import com.google.idea.blaze.base.lang.buildfile.psi.ParameterList;
 import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
 import com.google.idea.blaze.base.lang.buildfile.references.LabelReference;
 import com.intellij.psi.PsiElement;
+import java.util.Arrays;
+import javax.annotation.Nullable;
 
 /**
  * Additional error annotations, post parsing.
@@ -37,35 +41,52 @@
 
   @Override
   public void visitLoadStatement(LoadStatement node) {
-    StringLiteral[] strings = node.getChildStrings();
-    if (strings.length == 0) {
+    BuildElement[] children = node.buildElementChildren();
+    //    StringLiteral[] strings = node..getChildStrings();
+    if (children.length == 0) {
       return;
     }
-    PsiElement skylarkRef = new LabelReference(strings[0], false).resolve();
+    PsiElement skylarkRef = getSkylarkRef(children[0]);
     if (skylarkRef == null) {
-      markError(strings[0], "Cannot find this Skylark module");
+      markError(children[0], "Cannot find this Skylark module");
       return;
     }
     if (!(skylarkRef instanceof BuildFile)) {
-      markError(strings[0], strings[0].getText() + " is not a Skylark module");
+      markError(children[0], children[0].getText() + " is not a Skylark module");
       return;
     }
-    if (strings.length == 1) {
-      markError(node, "No definitions imported from Skylark module");
+
+    LoadedSymbol[] symbols =
+        Arrays.stream(children)
+            .filter(element -> element instanceof LoadedSymbol)
+            .toArray(LoadedSymbol[]::new);
+    if (symbols.length == 1) {
+      markError(node, "No symbols imported from Skylark module");
       return;
     }
     BuildFile skylarkModule = (BuildFile) skylarkRef;
-    for (int i = 1; i < strings.length; i++) {
-      String text = strings[i].getStringContents();
+    for (int i = 0; i < symbols.length; i++) {
+      String text = symbols[i].getSymbolString();
+      if (text == null) {
+        continue;
+      }
       FunctionStatement fn = skylarkModule.findDeclaredFunction(text);
       if (fn == null) {
         markError(
-            strings[i],
+            symbols[i],
             "Function '" + text + "' not found in Skylark module " + skylarkModule.getFileName());
       }
     }
   }
 
+  @Nullable
+  private static PsiElement getSkylarkRef(BuildElement firstChild) {
+    if (firstChild instanceof StringLiteral) {
+      return new LabelReference((StringLiteral) firstChild, false).resolve();
+    }
+    return null;
+  }
+
   @Override
   public void visitFuncallExpression(FuncallExpression node) {
     FunctionStatement function = (FunctionStatement) node.getReferencedElement();
diff --git a/base/src/com/google/idea/blaze/base/lang/buildfile/validation/HighlightingAnnotator.java b/base/src/com/google/idea/blaze/base/lang/buildfile/validation/HighlightingAnnotator.java
index c2a3f40..a2fba95 100644
--- a/base/src/com/google/idea/blaze/base/lang/buildfile/validation/HighlightingAnnotator.java
+++ b/base/src/com/google/idea/blaze/base/lang/buildfile/validation/HighlightingAnnotator.java
@@ -16,18 +16,17 @@
 package com.google.idea.blaze.base.lang.buildfile.validation;
 
 import com.google.idea.blaze.base.lang.buildfile.highlighting.BuildSyntaxHighlighter;
+import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuiltInNamesProvider;
 import com.google.idea.blaze.base.lang.buildfile.psi.Argument;
 import com.google.idea.blaze.base.lang.buildfile.psi.FunctionStatement;
 import com.google.idea.blaze.base.lang.buildfile.psi.Parameter;
+import com.google.idea.blaze.base.lang.buildfile.psi.ReferenceExpression;
 import com.intellij.lang.ASTNode;
 import com.intellij.lang.annotation.Annotation;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.util.PsiTreeUtil;
 
-/**
- * Additional syntax highlighting, based on parsed PSI elements TODO: Special highlighting for blaze
- * built-in names? (e.g. android_library) -- see PyBuiltInAnnotator
- */
+/** Additional syntax highlighting, based on parsed PSI elements. */
 public class HighlightingAnnotator extends BuildAnnotator {
 
   @Override
@@ -57,4 +56,15 @@
       annotation.setTextAttributes(BuildSyntaxHighlighter.BUILD_FN_DEFINITION);
     }
   }
+
+  @Override
+  public void visitReferenceExpression(ReferenceExpression node) {
+    ASTNode nameNode = node.getNameElement();
+    if (nameNode != null
+        && BuiltInNamesProvider.getBuiltInNames(node.getProject()).contains(nameNode.getText())) {
+      Annotation annotation = getHolder().createInfoAnnotation(nameNode, null);
+      annotation.setTextAttributes(BuildSyntaxHighlighter.BUILD_BUILTIN_NAME);
+    }
+    super.visitReferenceExpression(node);
+  }
 }
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/completion/ProjectViewKeywordCompletionContributor.java b/base/src/com/google/idea/blaze/base/lang/projectview/completion/ProjectViewKeywordCompletionContributor.java
index f1bf85a..88d8d2b 100644
--- a/base/src/com/google/idea/blaze/base/lang/projectview/completion/ProjectViewKeywordCompletionContributor.java
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/completion/ProjectViewKeywordCompletionContributor.java
@@ -17,6 +17,7 @@
 
 import static com.intellij.patterns.PlatformPatterns.psiElement;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import com.google.idea.blaze.base.lang.projectview.language.ProjectViewLanguage;
 import com.google.idea.blaze.base.lang.projectview.lexer.ProjectViewTokenType;
@@ -80,11 +81,18 @@
   private static List<LookupElement> getLookups() {
     ImmutableList.Builder<LookupElement> list = ImmutableList.builder();
     for (SectionParser parser : Sections.getUndeprecatedParsers()) {
-      list.add(forSectionParser(parser));
+      if (handledSectionType(parser)) {
+        list.add(forSectionParser(parser));
+      }
     }
     return list.build();
   }
 
+  @VisibleForTesting
+  public static boolean handledSectionType(SectionParser parser) {
+    return parser instanceof ListSectionParser || parser instanceof ScalarSectionParser;
+  }
+
   private static LookupElement forSectionParser(SectionParser parser) {
     return LookupElementBuilder.create(parser.getName()).withInsertHandler(insertDivider(parser));
   }
@@ -96,7 +104,7 @@
       context.commitDocument();
 
       String nextTokenText = findNextTokenText(context);
-      if (nextTokenText == null || nextTokenText == "\n") {
+      if (nextTokenText == null || nextTokenText.equals("\n")) {
         document.insertString(context.getTailOffset(), getDivider(parser));
         editor.getCaretModel().moveToOffset(context.getTailOffset());
       }
@@ -112,7 +120,7 @@
   }
 
   @Nullable
-  protected static String findNextTokenText(final InsertionContext context) {
+  private static String findNextTokenText(final InsertionContext context) {
     final PsiFile file = context.getFile();
     PsiElement element = file.findElementAt(context.getTailOffset());
     while (element != null && element.getTextLength() == 0) {
diff --git a/base/src/com/google/idea/blaze/base/lang/projectview/references/ProjectViewLabelReference.java b/base/src/com/google/idea/blaze/base/lang/projectview/references/ProjectViewLabelReference.java
index bcb06d0..d1748ec 100644
--- a/base/src/com/google/idea/blaze/base/lang/projectview/references/ProjectViewLabelReference.java
+++ b/base/src/com/google/idea/blaze/base/lang/projectview/references/ProjectViewLabelReference.java
@@ -26,13 +26,23 @@
 import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewPsiSectionItem;
 import com.google.idea.blaze.base.lang.projectview.psi.util.ProjectViewElementGenerator;
 import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.lang.ASTNode;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.TextRange;
 import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileSystem;
+import com.intellij.openapi.vfs.ex.temp.TempFileSystem;
 import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFileSystemItem;
+import com.intellij.psi.PsiManager;
 import com.intellij.psi.PsiReferenceBase;
 import com.intellij.util.ArrayUtil;
 import com.intellij.util.IncorrectOperationException;
+import java.io.File;
 import javax.annotation.Nullable;
 
 /** A blaze label reference. */
@@ -48,16 +58,41 @@
   @Nullable
   @Override
   public PsiElement resolve() {
+    if (pathFormat == PathFormat.NonLocalWithoutInitialBackslashes) {
+      return resolveFile(myElement.getText());
+    }
     Label label = getLabel(myElement.getText());
     if (label == null) {
       return null;
     }
-    return BuildReferenceManager.getInstance(myElement.getProject()).resolveLabel(label);
+    return BuildReferenceManager.getInstance(getProject()).resolveLabel(label);
   }
 
   @Nullable
-  private static Label getLabel(@Nullable String labelString) {
-    if (labelString == null || !labelString.startsWith("//") || labelString.indexOf('*') != -1) {
+  private PsiFileSystemItem resolveFile(String path) {
+    if (path.startsWith("/") || path.contains(":")) {
+      return null;
+    }
+    BuildReferenceManager manager = BuildReferenceManager.getInstance(getProject());
+    path = StringUtil.trimStart(path, "-");
+    File file = manager.resolvePackage(WorkspacePath.createIfValid(path));
+    if (file == null) {
+      return null;
+    }
+    VirtualFile vf = getFileSystem().findFileByPath(file.getPath());
+    if (vf == null) {
+      return null;
+    }
+    PsiManager psiManager = PsiManager.getInstance(getProject());
+    return vf.isDirectory() ? psiManager.findDirectory(vf) : psiManager.findFile(vf);
+  }
+
+  @Nullable
+  private Label getLabel(@Nullable String labelString) {
+    if (labelString == null
+        || !labelString.startsWith("//")
+        || labelString.contains("...")
+        || labelString.indexOf('*') != -1) {
       return null;
     }
     return LabelUtils.createLabelFromString(null, labelString);
@@ -75,8 +110,7 @@
     }
     String packagePrefix = LabelUtils.getPackagePathComponent(labelString);
     BuildFile referencedBuildFile =
-        BuildReferenceManager.getInstance(myElement.getProject())
-            .resolveBlazePackage(packagePrefix);
+        BuildReferenceManager.getInstance(getProject()).resolveBlazePackage(packagePrefix);
     if (referencedBuildFile == null) {
       return BuildLookupElement.EMPTY_ARRAY;
     }
@@ -93,8 +127,7 @@
     if (lookupData == null) {
       return BuildLookupElement.EMPTY_ARRAY;
     }
-    return BuildReferenceManager.getInstance(myElement.getProject())
-        .resolvePackageLookupElements(lookupData);
+    return BuildReferenceManager.getInstance(getProject()).resolvePackageLookupElements(lookupData);
   }
 
   @Override
@@ -109,7 +142,7 @@
     if (label == null) {
       return myElement;
     }
-    String ruleName = label.ruleName().toString();
+    String ruleName = label.targetName().toString();
     String newRuleName = newElementName;
 
     // handle subdirectories
@@ -133,4 +166,15 @@
     }
     return myElement;
   }
+
+  private Project getProject() {
+    return myElement.getProject();
+  }
+
+  private static VirtualFileSystem getFileSystem() {
+    if (ApplicationManager.getApplication().isUnitTestMode()) {
+      return TempFileSystem.getInstance();
+    }
+    return LocalFileSystem.getInstance();
+  }
 }
diff --git a/base/src/com/google/idea/blaze/base/metrics/Action.java b/base/src/com/google/idea/blaze/base/metrics/Action.java
index 89843df..b7f1ccf 100644
--- a/base/src/com/google/idea/blaze/base/metrics/Action.java
+++ b/base/src/com/google/idea/blaze/base/metrics/Action.java
@@ -58,7 +58,9 @@
 
   C_RESOLVE_FILE("crf"),
   BLAZE_CLION_TEST_RUN("ctr"),
-  BLAZE_CLION_TEST_DEBUG("ctd");
+  BLAZE_CLION_TEST_DEBUG("ctd"),
+
+  PYTHON_ACTIVE("pysync");
 
   @NotNull @NonNls private final String name;
 
diff --git a/java/src/com/google/idea/blaze/java/sync/model/BlazeLibrary.java b/base/src/com/google/idea/blaze/base/model/BlazeLibrary.java
similarity index 97%
rename from java/src/com/google/idea/blaze/java/sync/model/BlazeLibrary.java
rename to base/src/com/google/idea/blaze/base/model/BlazeLibrary.java
index f289717..6a227ce 100644
--- a/java/src/com/google/idea/blaze/java/sync/model/BlazeLibrary.java
+++ b/base/src/com/google/idea/blaze/base/model/BlazeLibrary.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.google.idea.blaze.java.sync.model;
+package com.google.idea.blaze.base.model;
 
 import com.google.common.base.Objects;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
diff --git a/base/src/com/google/idea/blaze/base/model/BlazeProjectData.java b/base/src/com/google/idea/blaze/base/model/BlazeProjectData.java
index 8a2f278..84f6936 100644
--- a/base/src/com/google/idea/blaze/base/model/BlazeProjectData.java
+++ b/base/src/com/google/idea/blaze/base/model/BlazeProjectData.java
@@ -15,9 +15,10 @@
  */
 package com.google.idea.blaze.base.model;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMultimap;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
 import com.google.idea.blaze.base.sync.workspace.BlazeRoots;
@@ -30,32 +31,35 @@
 /** The top-level object serialized to cache. */
 @Immutable
 public class BlazeProjectData implements Serializable {
-  private static final long serialVersionUID = 23L;
+  private static final long serialVersionUID = 25L;
 
   public final long syncTime;
-  public final RuleMap ruleMap;
+  public final TargetMap targetMap;
+  public final ImmutableMap<String, String> blazeInfo;
   public final BlazeRoots blazeRoots;
   @Nullable public final WorkingSet workingSet;
   public final WorkspacePathResolver workspacePathResolver;
   public final ArtifactLocationDecoder artifactLocationDecoder;
   public final WorkspaceLanguageSettings workspaceLanguageSettings;
   public final SyncState syncState;
-  public final ImmutableMultimap<RuleKey, RuleKey> reverseDependencies;
+  public final ImmutableMultimap<TargetKey, TargetKey> reverseDependencies;
   @Nullable public final String vcsName;
 
   public BlazeProjectData(
       long syncTime,
-      RuleMap ruleMap,
+      TargetMap targetMap,
+      ImmutableMap<String, String> blazeInfo,
       BlazeRoots blazeRoots,
       @Nullable WorkingSet workingSet,
       WorkspacePathResolver workspacePathResolver,
       ArtifactLocationDecoder artifactLocationDecoder,
       WorkspaceLanguageSettings workspaceLangaugeSettings,
       SyncState syncState,
-      ImmutableMultimap<RuleKey, RuleKey> reverseDependencies,
+      ImmutableMultimap<TargetKey, TargetKey> reverseDependencies,
       String vcsName) {
     this.syncTime = syncTime;
-    this.ruleMap = ruleMap;
+    this.targetMap = targetMap;
+    this.blazeInfo = blazeInfo;
     this.blazeRoots = blazeRoots;
     this.workingSet = workingSet;
     this.workspacePathResolver = workspacePathResolver;
diff --git a/base/src/com/google/idea/blaze/base/model/BlazeWorkspaceType.java b/base/src/com/google/idea/blaze/base/model/BlazeWorkspaceType.java
deleted file mode 100644
index d57270a..0000000
--- a/base/src/com/google/idea/blaze/base/model/BlazeWorkspaceType.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2016 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.idea.blaze.base.model;
-
-enum BlazeWorkspaceType {
-  BASE,
-  JAVA,
-  ANDROID
-}
diff --git a/java/src/com/google/idea/blaze/java/sync/model/LibraryKey.java b/base/src/com/google/idea/blaze/base/model/LibraryKey.java
similarity index 97%
rename from java/src/com/google/idea/blaze/java/sync/model/LibraryKey.java
rename to base/src/com/google/idea/blaze/base/model/LibraryKey.java
index fe19973..067e6ea 100644
--- a/java/src/com/google/idea/blaze/java/sync/model/LibraryKey.java
+++ b/base/src/com/google/idea/blaze/base/model/LibraryKey.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.google.idea.blaze.java.sync.model;
+package com.google.idea.blaze.base.model;
 
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
 import com.intellij.openapi.roots.libraries.Library;
diff --git a/base/src/com/google/idea/blaze/base/model/primitives/Kind.java b/base/src/com/google/idea/blaze/base/model/primitives/Kind.java
index 80186c9..afe9302 100644
--- a/base/src/com/google/idea/blaze/base/model/primitives/Kind.java
+++ b/base/src/com/google/idea/blaze/base/model/primitives/Kind.java
@@ -30,9 +30,7 @@
   JAVA_BINARY("java_binary", LanguageClass.JAVA),
   JAVA_IMPORT("java_import", LanguageClass.JAVA),
   JAVA_TOOLCHAIN("java_toolchain", LanguageClass.JAVA),
-  PROTO_LIBRARY(
-      "proto_library",
-      LanguageClass.JAVA), // The LanguageClass might have to change if we support other languages
+  PROTO_LIBRARY("proto_library", LanguageClass.GENERIC),
   JAVA_PLUGIN("java_plugin", LanguageClass.JAVA),
   ANDROID_RESOURCES("android_resources", LanguageClass.ANDROID),
   CC_LIBRARY("cc_library", LanguageClass.C),
@@ -46,6 +44,10 @@
   GWT_MODULE("gwt_module", LanguageClass.JAVA),
   GWT_TEST("gwt_test", LanguageClass.JAVA),
   TEST_SUITE("test_suite", LanguageClass.GENERIC),
+  PY_LIBRARY("py_library", LanguageClass.PYTHON),
+  PY_BINARY("py_binary", LanguageClass.PYTHON),
+  PY_TEST("py_test", LanguageClass.PYTHON),
+  PY_APPENGINE_BINARY("py_appengine_binary", LanguageClass.PYTHON),
   ;
 
   static final ImmutableMap<String, Kind> STRING_TO_KIND = makeStringToKindMap();
@@ -91,4 +93,13 @@
     }
     return false;
   }
+
+  /** Uses the heuristic that test rules are either 'test_suite', or end in '_test' */
+  public static boolean isTestRule(String ruleType) {
+    return isTestSuite(ruleType) || ruleType.endsWith("_test");
+  }
+
+  public static boolean isTestSuite(String ruleType) {
+    return "test_suite".equals(ruleType);
+  }
 }
diff --git a/base/src/com/google/idea/blaze/base/model/primitives/Label.java b/base/src/com/google/idea/blaze/base/model/primitives/Label.java
index 302a27d..c5f31ab 100644
--- a/base/src/com/google/idea/blaze/base/model/primitives/Label.java
+++ b/base/src/com/google/idea/blaze/base/model/primitives/Label.java
@@ -47,8 +47,8 @@
     }
   }
 
-  public Label(WorkspacePath packageName, RuleName newRuleName) {
-    this("//" + packageName.toString() + ":" + newRuleName.toString());
+  public Label(WorkspacePath packageName, TargetName newTargetName) {
+    this("//" + packageName.toString() + ":" + newTargetName.toString());
   }
 
   public static boolean validate(String label) {
@@ -63,7 +63,7 @@
         return false;
       }
       String ruleName = label.substring(colonIndex + 1);
-      if (!RuleName.validate(ruleName, errors)) {
+      if (!TargetName.validate(ruleName, errors)) {
         return false;
       }
       return true;
@@ -82,16 +82,16 @@
   }
 
   /**
-   * Extract the rule name from a label. The rule name follows a colon at the end of the label.
+   * Extract the target name from a label. The target name follows a colon at the end of the label.
    *
-   * @return the rule name
+   * @return the target name
    */
-  public RuleName ruleName() {
+  public TargetName targetName() {
     String labelStr = toString();
     int colonLocation = labelStr.lastIndexOf(':');
-    int ruleNameStart = colonLocation + 1;
-    String ruleNameStr = labelStr.substring(ruleNameStart);
-    return RuleName.create(ruleNameStr);
+    int targetNameStart = colonLocation + 1;
+    String targetNameStr = labelStr.substring(targetNameStart);
+    return TargetName.create(targetNameStr);
   }
 
   /**
diff --git a/base/src/com/google/idea/blaze/base/model/primitives/LanguageClass.java b/base/src/com/google/idea/blaze/base/model/primitives/LanguageClass.java
index 4ea1df8..f174a4f 100644
--- a/base/src/com/google/idea/blaze/base/model/primitives/LanguageClass.java
+++ b/base/src/com/google/idea/blaze/base/model/primitives/LanguageClass.java
@@ -23,7 +23,8 @@
   ANDROID("android"),
   JAVASCRIPT("javascript"),
   TYPESCRIPT("typescript"),
-  DART("dart");
+  DART("dart"),
+  PYTHON("python");
 
   private final String name;
 
diff --git a/base/src/com/google/idea/blaze/base/model/primitives/RuleName.java b/base/src/com/google/idea/blaze/base/model/primitives/TargetName.java
similarity index 71%
rename from base/src/com/google/idea/blaze/base/model/primitives/RuleName.java
rename to base/src/com/google/idea/blaze/base/model/primitives/TargetName.java
index b535a2c..87dd1fe 100644
--- a/base/src/com/google/idea/blaze/base/model/primitives/RuleName.java
+++ b/base/src/com/google/idea/blaze/base/model/primitives/TargetName.java
@@ -24,8 +24,8 @@
 import java.util.regex.Pattern;
 import javax.annotation.Nullable;
 
-/** The rule name part of a label */
-public final class RuleName {
+/** The target name part of a label */
+public final class TargetName {
 
   // This is a subset of the allowable target names in Blaze
   private static final String ALNUM_REGEX_STR = "[a-zA-Z0-9]*";
@@ -38,102 +38,105 @@
 
   private final String name;
 
-  private RuleName(String ruleName) {
+  private TargetName(String ruleName) {
     this.name = ruleName;
   }
 
-  /** Silently returns null if the string is not a valid rule name. */
+  /** Silently returns null if the string is not a valid target name. */
   @Nullable
-  public static RuleName createIfValid(String ruleName) {
-    if (validate(ruleName, null)) {
-      return new RuleName(ruleName);
+  public static TargetName createIfValid(String targetName) {
+    if (validate(targetName, null)) {
+      return new TargetName(targetName);
     }
     return null;
   }
 
-  public static RuleName create(String ruleName) {
+  public static TargetName create(String targetName) {
     List<BlazeValidationError> errors = Lists.newArrayList();
-    if (!validate(ruleName, errors)) {
+    if (!validate(targetName, errors)) {
       BlazeValidationError.throwError(errors);
     }
-    return new RuleName(ruleName);
+    return new TargetName(targetName);
   }
 
   /** Validates a rule name using the same logic as Blaze */
-  public static boolean validate(String ruleName) {
-    return validate(ruleName, null);
+  public static boolean validate(String targetName) {
+    return validate(targetName, null);
   }
 
   /** Validates a rule name using the same logic as Blaze */
   public static boolean validate(
-      String ruleName, @Nullable Collection<BlazeValidationError> errors) {
-    if (ruleName.isEmpty()) {
+      String targetName, @Nullable Collection<BlazeValidationError> errors) {
+    if (targetName.isEmpty()) {
       BlazeValidationError.collect(
           errors, new BlazeValidationError("target names cannot be empty"));
       return false;
     }
     // Forbidden start chars:
-    if (ruleName.charAt(0) == '/') {
+    if (targetName.charAt(0) == '/') {
       BlazeValidationError.collect(
           errors,
           new BlazeValidationError(
-              "Invalid target name: " + ruleName + "\n" + "target names may not start with \"/\""));
+              "Invalid target name: "
+                  + targetName
+                  + "\n"
+                  + "target names may not start with \"/\""));
       return false;
-    } else if (ruleName.charAt(0) == '.') {
-      if (ruleName.startsWith("../") || ruleName.equals("..")) {
+    } else if (targetName.charAt(0) == '.') {
+      if (targetName.startsWith("../") || targetName.equals("..")) {
         BlazeValidationError.collect(
             errors,
             new BlazeValidationError(
                 "Invalid target name: "
-                    + ruleName
+                    + targetName
                     + "\n"
                     + "target names may not contain up-level references \"..\""));
         return false;
-      } else if (ruleName.equals(".")) {
+      } else if (targetName.equals(".")) {
         return true;
-      } else if (ruleName.startsWith("./")) {
+      } else if (targetName.startsWith("./")) {
         BlazeValidationError.collect(
             errors,
             new BlazeValidationError(
                 "Invalid target name: "
-                    + ruleName
+                    + targetName
                     + "\n"
                     + "target names may not contain \".\" as a path segment"));
         return false;
       }
     }
 
-    for (int i = 0; i < ruleName.length(); ++i) {
-      char c = ruleName.charAt(i);
+    for (int i = 0; i < targetName.length(); ++i) {
+      char c = targetName.charAt(i);
       if (ALLOWED_META.contains(c)) {
         continue;
       }
       if (c == '/') {
         // Forbidden substrings: "/../", "/./", "//"
-        if (ruleName.contains("/../")) {
+        if (targetName.contains("/../")) {
           BlazeValidationError.collect(
               errors,
               new BlazeValidationError(
                   "Invalid target name: "
-                      + ruleName
+                      + targetName
                       + "\n"
                       + "target names may not contain up-level references \"..\""));
           return false;
-        } else if (ruleName.contains("/./")) {
+        } else if (targetName.contains("/./")) {
           BlazeValidationError.collect(
               errors,
               new BlazeValidationError(
                   "Invalid target name: "
-                      + ruleName
+                      + targetName
                       + "\n"
                       + "target names may not contain \".\" as a path segment"));
           return false;
-        } else if (ruleName.contains("//")) {
+        } else if (targetName.contains("//")) {
           BlazeValidationError.collect(
               errors,
               new BlazeValidationError(
                   "Invalid target name: "
-                      + ruleName
+                      + targetName
                       + "\n"
                       + "target names may not contain \"//\" path separators"));
           return false;
@@ -145,28 +148,28 @@
         BlazeValidationError.collect(
             errors,
             new BlazeValidationError(
-                "Invalid target name: " + ruleName + "\n" + "target names may not contain " + c));
+                "Invalid target name: " + targetName + "\n" + "target names may not contain " + c));
         return false;
       }
     }
 
     // Forbidden end chars:
-    if (ruleName.endsWith("/..")) {
+    if (targetName.endsWith("/..")) {
       BlazeValidationError.collect(
           errors,
           new BlazeValidationError(
               "Invalid target name: "
-                  + ruleName
+                  + targetName
                   + "\n"
                   + "target names may not contain up-level references \"..\""));
       return false;
-    } else if (ruleName.endsWith("/.")) {
+    } else if (targetName.endsWith("/.")) {
       return true;
-    } else if (ruleName.endsWith("/")) {
+    } else if (targetName.endsWith("/")) {
       BlazeValidationError.collect(
           errors,
           new BlazeValidationError(
-              "Invalid target name: " + ruleName + "\n" + "target names may not end with \"/\""));
+              "Invalid target name: " + targetName + "\n" + "target names may not end with \"/\""));
       return false;
     }
     return true;
@@ -187,8 +190,8 @@
     if (this == obj) {
       return true;
     }
-    if (obj instanceof RuleName) {
-      RuleName that = (RuleName) obj;
+    if (obj instanceof TargetName) {
+      TargetName that = (TargetName) obj;
       return Objects.equal(name, that.name);
     }
     return false;
diff --git a/base/src/com/google/idea/blaze/base/model/primitives/WorkspaceType.java b/base/src/com/google/idea/blaze/base/model/primitives/WorkspaceType.java
index 1e69fce..293aeb0 100644
--- a/base/src/com/google/idea/blaze/base/model/primitives/WorkspaceType.java
+++ b/base/src/com/google/idea/blaze/base/model/primitives/WorkspaceType.java
@@ -18,18 +18,20 @@
 /**
  * Workspace types.
  *
- * <p>If the user doesn't specify a workspace, she gets the highest supported workspace type by enum
- * ordinal.
+ * <p>If the user doesn't specify a workspace, she gets the supported workspace type with lowest
+ * enum ordinal.
  */
 public enum WorkspaceType {
-  INTELLIJ_PLUGIN("intellij_plugin", LanguageClass.JAVA),
-  C("c", LanguageClass.C),
-  JAVA("java", LanguageClass.JAVA),
   ANDROID_NDK("android_ndk", LanguageClass.ANDROID, LanguageClass.JAVA, LanguageClass.C),
   ANDROID("android", LanguageClass.ANDROID, LanguageClass.JAVA),
-  JAVASCRIPT("javascript");
+  C("c", LanguageClass.C),
+  JAVA("java", LanguageClass.JAVA),
+  PYTHON("python", LanguageClass.PYTHON),
+  JAVASCRIPT("javascript", LanguageClass.JAVASCRIPT),
+  INTELLIJ_PLUGIN("intellij_plugin", LanguageClass.JAVA);
 
   private final String name;
+  // the languages active by default for this WorkspaceType
   private final LanguageClass[] languages;
 
   WorkspaceType(String name, LanguageClass... languages) {
diff --git a/base/src/com/google/idea/blaze/base/projectview/ProjectViewVerifier.java b/base/src/com/google/idea/blaze/base/projectview/ProjectViewVerifier.java
index 4d7d56a..553900d 100644
--- a/base/src/com/google/idea/blaze/base/projectview/ProjectViewVerifier.java
+++ b/base/src/com/google/idea/blaze/base/projectview/ProjectViewVerifier.java
@@ -16,7 +16,7 @@
 package com.google.idea.blaze.base.projectview;
 
 import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.io.WorkspaceScanner;
+import com.google.idea.blaze.base.io.FileAttributeProvider;
 import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.projectview.section.ListSection;
@@ -47,9 +47,15 @@
       WorkspaceRoot workspaceRoot,
       ProjectViewSet projectViewSet,
       WorkspaceLanguageSettings workspaceLanguageSettings) {
-    if (!verifyIncludedPackagesExistOnDisk(context, workspaceRoot, projectViewSet)) {
-      return false;
-    }
+    return verifyProjectViewNoDisk(context, projectViewSet, workspaceLanguageSettings)
+        && verifyIncludedPackagesExistOnDisk(context, workspaceRoot, projectViewSet);
+  }
+
+  /** Verifies the project view, without hitting disk. */
+  public static boolean verifyProjectViewNoDisk(
+      BlazeContext context,
+      ProjectViewSet projectViewSet,
+      WorkspaceLanguageSettings workspaceLanguageSettings) {
     if (!verifyIncludedPackagesAreNotExcluded(context, projectViewSet)) {
       return false;
     }
@@ -116,7 +122,7 @@
       BlazeContext context, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet) {
     boolean ok = true;
 
-    WorkspaceScanner workspaceScanner = WorkspaceScanner.getInstance();
+    FileAttributeProvider fileAttributeProvider = FileAttributeProvider.getInstance();
 
     for (ProjectViewSet.ProjectViewFile projectViewFile : projectViewSet.getProjectViewFiles()) {
       List<DirectoryEntry> directoryEntries = Lists.newArrayList();
@@ -129,7 +135,7 @@
           continue;
         }
         WorkspacePath workspacePath = entry.directory;
-        if (!workspaceScanner.exists(workspaceRoot, workspacePath)) {
+        if (!fileAttributeProvider.exists(workspaceRoot.fileForPath(workspacePath))) {
           IssueOutput.error(
                   String.format(
                       "Directory '%s' specified in import roots not found "
diff --git a/base/src/com/google/idea/blaze/base/rulemaps/ReverseDependencyMap.java b/base/src/com/google/idea/blaze/base/rulemaps/ReverseDependencyMap.java
deleted file mode 100644
index 1941eea..0000000
--- a/base/src/com/google/idea/blaze/base/rulemaps/ReverseDependencyMap.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2016 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.idea.blaze.base.rulemaps;
-
-import com.google.common.collect.ImmutableMultimap;
-import com.google.common.collect.Iterables;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
-import com.google.idea.blaze.base.model.primitives.Label;
-
-/** Handy class to create an reverse dep map of all rules */
-public class ReverseDependencyMap {
-  public static ImmutableMultimap<RuleKey, RuleKey> createRdepsMap(RuleMap ruleMap) {
-    ImmutableMultimap.Builder<RuleKey, RuleKey> builder = ImmutableMultimap.builder();
-    for (RuleIdeInfo rule : ruleMap.rules()) {
-      RuleKey key = rule.key;
-      for (Label dep : Iterables.concat(rule.dependencies, rule.runtimeDeps)) {
-        RuleKey depKey = RuleKey.forDependency(rule, dep);
-        if (ruleMap.contains(depKey)) {
-          builder.put(depKey, key);
-        }
-      }
-    }
-    return builder.build();
-  }
-}
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeBuildTargetRunConfigurationFactory.java b/base/src/com/google/idea/blaze/base/run/BlazeBuildTargetRunConfigurationFactory.java
index 61749cf..afb6e79 100644
--- a/base/src/com/google/idea/blaze/base/run/BlazeBuildTargetRunConfigurationFactory.java
+++ b/base/src/com/google/idea/blaze/base/run/BlazeBuildTargetRunConfigurationFactory.java
@@ -29,8 +29,8 @@
 public class BlazeBuildTargetRunConfigurationFactory extends BlazeRunConfigurationFactory {
 
   @Override
-  public boolean handlesTarget(Project project, BlazeProjectData blazeProjectData, Label target) {
-    return BlazeBuildFileRunConfigurationProducer.handlesTarget(project, target);
+  public boolean handlesTarget(Project project, BlazeProjectData blazeProjectData, Label label) {
+    return BlazeBuildFileRunConfigurationProducer.handlesTarget(project, label);
   }
 
   @Override
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeCommandRunConfiguration.java b/base/src/com/google/idea/blaze/base/run/BlazeCommandRunConfiguration.java
index 5405d02..d6200ee 100644
--- a/base/src/com/google/idea/blaze/base/run/BlazeCommandRunConfiguration.java
+++ b/base/src/com/google/idea/blaze/base/run/BlazeCommandRunConfiguration.java
@@ -16,16 +16,18 @@
 package com.google.idea.blaze.base.run;
 
 import com.google.common.base.Strings;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.model.primitives.TargetExpression;
 import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandler;
 import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandlerProvider;
 import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationRunner;
-import com.google.idea.blaze.base.run.rulefinder.RuleFinder;
 import com.google.idea.blaze.base.run.state.RunConfigurationState;
 import com.google.idea.blaze.base.run.state.RunConfigurationStateEditor;
+import com.google.idea.blaze.base.run.targetfinder.TargetFinder;
 import com.google.idea.blaze.base.settings.Blaze;
 import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
 import com.google.idea.blaze.base.ui.UiUtil;
@@ -44,9 +46,12 @@
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.InvalidDataException;
 import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.ui.TextFieldWithAutoCompletion;
+import com.intellij.ui.TextFieldWithAutoCompletion.StringsCompletionProvider;
 import com.intellij.ui.components.JBLabel;
-import com.intellij.ui.components.JBTextField;
 import com.intellij.util.ui.UIUtil;
+import java.util.Collection;
+import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
 import javax.annotation.Nullable;
@@ -59,11 +64,18 @@
 /** A run configuration which executes Blaze commands. */
 public class BlazeCommandRunConfiguration extends LocatableConfigurationBase
     implements BlazeRunConfiguration, RunnerIconProvider {
+
   private static final Logger LOG = Logger.getInstance(BlazeCommandRunConfiguration.class);
 
   private static final String HANDLER_ATTR = "handler-id";
   private static final String TARGET_TAG = "blaze-target";
   private static final String KIND_ATTR = "kind";
+  /**
+   * This tag is actually written by {@link com.intellij.execution.impl.RunManagerImpl}; it
+   * represents the before-run tasks of the configuration. We need to know about it to avoid writing
+   * it ourselves.
+   */
+  private static final String METHOD_TAG = "method";
 
   /** The last serialized state of the configuration. */
   private Element elementState = new Element("dummy");
@@ -147,8 +159,9 @@
   @Nullable
   public Kind getKindForTarget() {
     if (target instanceof Label) {
-      RuleIdeInfo rule = RuleFinder.getInstance().ruleForTarget(getProject(), (Label) target);
-      return rule != null ? rule.kind : null;
+      TargetIdeInfo target =
+          TargetFinder.getInstance().targetForLabel(getProject(), (Label) this.target);
+      return target != null ? target.kind : null;
     }
     return null;
   }
@@ -253,6 +266,10 @@
     }
     Set<String> baseChildren =
         element.getChildren().stream().map(Element::getName).collect(Collectors.toSet());
+    // The method tag is written by RunManagerImpl *after* this writeExternal call,
+    // so it isn't already present.
+    // We still have to avoid writing it ourselves, or we wind up duplicating it.
+    baseChildren.add(METHOD_TAG);
     for (Element child : elementState.getChildren()) {
       if (!baseChildren.contains(child.getName())) {
         element.addContent(child.clone());
@@ -317,13 +334,16 @@
 
     private final Box editor;
     private final JBLabel targetExpressionLabel;
-    private final JBTextField targetField = new JBTextField(1);
+    private final TextFieldWithAutoCompletion<String> targetField;
 
     BlazeCommandRunConfigurationSettingsEditor(BlazeCommandRunConfiguration config) {
+      Project project = config.getProject();
+      targetField =
+          new TextFieldWithAutoCompletion<>(
+              project, new TargetCompletionProvider(project), true, null);
       elementState = config.elementState.clone();
       targetExpressionLabel = new JBLabel(UIUtil.ComponentStyle.LARGE);
       editor = UiUtil.createBox(targetExpressionLabel, targetField);
-      targetField.getEmptyText().setText("Full target expression starting with //");
       updateTargetExpressionLabel(config);
       updateHandlerEditor(config);
     }
@@ -400,4 +420,24 @@
       }
     }
   }
+
+  private static class TargetCompletionProvider extends StringsCompletionProvider {
+    TargetCompletionProvider(Project project) {
+      super(getTargets(project), null);
+    }
+
+    private static Collection<String> getTargets(Project project) {
+      List<String> result = Lists.newArrayList();
+      BlazeProjectData projectData =
+          BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+      if (projectData != null) {
+        for (TargetIdeInfo target : projectData.targetMap.targets()) {
+          if (target.isPlainTarget()) {
+            result.add(target.key.label.toString());
+          }
+        }
+      }
+      return result;
+    }
+  }
 }
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeConfigurationNameBuilder.java b/base/src/com/google/idea/blaze/base/run/BlazeConfigurationNameBuilder.java
index 4b7c9d0..428b1cf 100644
--- a/base/src/com/google/idea/blaze/base/run/BlazeConfigurationNameBuilder.java
+++ b/base/src/com/google/idea/blaze/base/run/BlazeConfigurationNameBuilder.java
@@ -78,7 +78,7 @@
    */
   public BlazeConfigurationNameBuilder setTargetString(Label label) {
     this.targetString =
-        String.format("%s:%s", getImmediatePackage(label), label.ruleName().toString());
+        String.format("%s:%s", getImmediatePackage(label), label.targetName().toString());
     return this;
   }
 
diff --git a/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationFactory.java b/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationFactory.java
index 1010640..6a8d573 100644
--- a/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationFactory.java
+++ b/base/src/com/google/idea/blaze/base/run/BlazeRunConfigurationFactory.java
@@ -31,7 +31,7 @@
 
   /** Returns whether this factory can handle a target. */
   public abstract boolean handlesTarget(
-      Project project, BlazeProjectData blazeProjectData, Label target);
+      Project project, BlazeProjectData blazeProjectData, Label label);
 
   /**
    * Returns whether this factory can initialize a configuration. <br>
diff --git a/base/src/com/google/idea/blaze/base/run/RuleNameHeuristic.java b/base/src/com/google/idea/blaze/base/run/TargetNameHeuristic.java
similarity index 73%
rename from base/src/com/google/idea/blaze/base/run/RuleNameHeuristic.java
rename to base/src/com/google/idea/blaze/base/run/TargetNameHeuristic.java
index f766ceb..c2cf40a 100644
--- a/base/src/com/google/idea/blaze/base/run/RuleNameHeuristic.java
+++ b/base/src/com/google/idea/blaze/base/run/TargetNameHeuristic.java
@@ -15,23 +15,23 @@
  */
 package com.google.idea.blaze.base.run;
 
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.ideinfo.TestIdeInfo.TestSize;
 import com.intellij.openapi.util.io.FileUtil;
 import java.io.File;
 import javax.annotation.Nullable;
 
 /** Looks for a test rule with rule name matching the source file. */
-public class RuleNameHeuristic implements TestRuleHeuristic {
+public class TargetNameHeuristic implements TestTargetHeuristic {
 
   @Override
-  public boolean matchesSource(RuleIdeInfo rule, File sourceFile, @Nullable TestSize testSize) {
+  public boolean matchesSource(TargetIdeInfo target, File sourceFile, @Nullable TestSize testSize) {
     String filePathWithoutExtension = FileUtil.getNameWithoutExtension(sourceFile.getPath());
-    String ruleName = rule.label.ruleName().toString();
-    if (!filePathWithoutExtension.endsWith(ruleName)) {
+    String targetName = target.key.label.targetName().toString();
+    if (!filePathWithoutExtension.endsWith(targetName)) {
       return false;
     }
-    int i = filePathWithoutExtension.length() - ruleName.length() - 1;
+    int i = filePathWithoutExtension.length() - targetName.length() - 1;
     if (i < 0) {
       // Equal length
       return true;
diff --git a/base/src/com/google/idea/blaze/base/run/TestRuleHeuristic.java b/base/src/com/google/idea/blaze/base/run/TestRuleHeuristic.java
deleted file mode 100644
index 67e6813..0000000
--- a/base/src/com/google/idea/blaze/base/run/TestRuleHeuristic.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2016 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.idea.blaze.base.run;
-
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.TestIdeInfo;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.intellij.openapi.extensions.ExtensionPointName;
-import java.io.File;
-import java.util.Collection;
-import javax.annotation.Nullable;
-
-/** Heuristic to match test rules to source files. */
-public interface TestRuleHeuristic {
-
-  ExtensionPointName<TestRuleHeuristic> EP_NAME =
-      ExtensionPointName.create("com.google.idea.blaze.TestRuleHeuristic");
-
-  /**
-   * Given a source file and all test rules reachable from that file, chooses a test rule based on
-   * available filters, falling back to choosing the first one if there is no match.
-   */
-  @Nullable
-  static Label chooseTestTargetForSourceFile(
-      File sourceFile, Collection<RuleIdeInfo> rules, @Nullable TestIdeInfo.TestSize testSize) {
-
-    for (TestRuleHeuristic filter : EP_NAME.getExtensions()) {
-      RuleIdeInfo match =
-          rules
-              .stream()
-              .filter(rule -> filter.matchesSource(rule, sourceFile, testSize))
-              .findFirst()
-              .orElse(null);
-
-      if (match != null) {
-        return match.label;
-      }
-    }
-    return rules.isEmpty() ? null : rules.iterator().next().label;
-  }
-
-  /** Returns true if the rule and source file match, according to this heuristic. */
-  boolean matchesSource(RuleIdeInfo rule, File sourceFile, @Nullable TestIdeInfo.TestSize testSize);
-}
diff --git a/base/src/com/google/idea/blaze/base/run/TestSizeHeuristic.java b/base/src/com/google/idea/blaze/base/run/TestSizeHeuristic.java
index 1e1b56d..67f899a 100644
--- a/base/src/com/google/idea/blaze/base/run/TestSizeHeuristic.java
+++ b/base/src/com/google/idea/blaze/base/run/TestSizeHeuristic.java
@@ -15,20 +15,20 @@
  */
 package com.google.idea.blaze.base.run;
 
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.ideinfo.TestIdeInfo;
 import com.google.idea.blaze.base.ideinfo.TestIdeInfo.TestSize;
 import java.io.File;
 import javax.annotation.Nullable;
 
 /** Matches source files to test rules based on size annotations/tags. */
-public class TestSizeHeuristic implements TestRuleHeuristic {
+public class TestSizeHeuristic implements TestTargetHeuristic {
 
   @Override
-  public boolean matchesSource(RuleIdeInfo rule, File sourceFile, @Nullable TestSize testSize) {
+  public boolean matchesSource(TargetIdeInfo target, File sourceFile, @Nullable TestSize testSize) {
     // If testSize == null then prefer small
     // Some test runners will assume no size annotation == small and filter on that, others will not
     TestSize size = testSize != null ? testSize : TestIdeInfo.DEFAULT_NON_ANNOTATED_TEST_SIZE;
-    return TestIdeInfo.getTestSize(rule) == size;
+    return TestIdeInfo.getTestSize(target) == size;
   }
 }
diff --git a/base/src/com/google/idea/blaze/base/run/TestRuleFinder.java b/base/src/com/google/idea/blaze/base/run/TestTargetFinder.java
similarity index 77%
rename from base/src/com/google/idea/blaze/base/run/TestRuleFinder.java
rename to base/src/com/google/idea/blaze/base/run/TestTargetFinder.java
index 4049462..c6dc317 100644
--- a/base/src/com/google/idea/blaze/base/run/TestRuleFinder.java
+++ b/base/src/com/google/idea/blaze/base/run/TestTargetFinder.java
@@ -15,23 +15,23 @@
  */
 package com.google.idea.blaze.base.run;
 
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.project.Project;
 import java.io.File;
 import java.util.Collection;
 
 /** Locates test rules for a given file. */
-public interface TestRuleFinder {
+public interface TestTargetFinder {
 
-  static TestRuleFinder getInstance(Project project) {
-    return ServiceManager.getService(project, TestRuleFinder.class);
+  static TestTargetFinder getInstance(Project project) {
+    return ServiceManager.getService(project, TestTargetFinder.class);
   }
 
   /**
    * Finds all test rules 'reachable' from source file (i.e. with source included in srcs, deps or
    * runtime_deps).
    */
-  Collection<RuleIdeInfo> testTargetsForSourceFile(File sourceFile);
+  Collection<TargetIdeInfo> testTargetsForSourceFile(File sourceFile);
 
 }
diff --git a/base/src/com/google/idea/blaze/base/run/TestTargetHeuristic.java b/base/src/com/google/idea/blaze/base/run/TestTargetHeuristic.java
new file mode 100644
index 0000000..9f31b39
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/TestTargetHeuristic.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.run;
+
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TestIdeInfo;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import java.io.File;
+import java.util.Collection;
+import javax.annotation.Nullable;
+
+/** Heuristic to match test targets to source files. */
+public interface TestTargetHeuristic {
+
+  ExtensionPointName<TestTargetHeuristic> EP_NAME =
+      ExtensionPointName.create("com.google.idea.blaze.TestTargetHeuristic");
+
+  /** Finds a test rule associated with a given {@link PsiElement}. */
+  @Nullable
+  static Label testTargetForPsiElement(@Nullable PsiElement element) {
+    if (element == null) {
+      return null;
+    }
+    File file = getContainingFile(element);
+    if (file == null) {
+      return null;
+    }
+    Collection<TargetIdeInfo> rules =
+        TestTargetFinder.getInstance(element.getProject()).testTargetsForSourceFile(file);
+    return chooseTestTargetForSourceFile(file, rules, null);
+  }
+
+  static File getContainingFile(PsiElement element) {
+    PsiFile psiFile = element.getContainingFile();
+    if (psiFile == null) {
+      return null;
+    }
+    VirtualFile vf = psiFile.getVirtualFile();
+    return vf != null ? new File(vf.getPath()) : null;
+  }
+
+  /**
+   * Given a source file and all test rules reachable from that file, chooses a test rule based on
+   * available filters, falling back to choosing the first one if there is no match.
+   */
+  @Nullable
+  static Label chooseTestTargetForSourceFile(
+      File sourceFile, Collection<TargetIdeInfo> targets, @Nullable TestIdeInfo.TestSize testSize) {
+
+    for (TestTargetHeuristic filter : EP_NAME.getExtensions()) {
+      TargetIdeInfo match =
+          targets
+              .stream()
+              .filter(target -> filter.matchesSource(target, sourceFile, testSize))
+              .findFirst()
+              .orElse(null);
+
+      if (match != null) {
+        return match.key.label;
+      }
+    }
+    return targets.isEmpty() ? null : targets.iterator().next().key.label;
+  }
+
+  /** Returns true if the rule and source file match, according to this heuristic. */
+  boolean matchesSource(
+      TargetIdeInfo target, File sourceFile, @Nullable TestIdeInfo.TestSize testSize);
+}
diff --git a/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationRunner.java b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationRunner.java
index ed40a0d..79a5147 100644
--- a/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationRunner.java
+++ b/base/src/com/google/idea/blaze/base/run/confighandler/BlazeCommandGenericRunConfigurationRunner.java
@@ -65,11 +65,11 @@
   }
 
   /** {@link RunProfileState} for generic blaze commands. */
-  private static class BlazeCommandRunProfileState extends CommandLineState {
+  public static class BlazeCommandRunProfileState extends CommandLineState {
     private final BlazeCommandRunConfiguration configuration;
     private final BlazeCommandRunConfigurationCommonState handlerState;
 
-    BlazeCommandRunProfileState(ExecutionEnvironment environment) {
+    public BlazeCommandRunProfileState(ExecutionEnvironment environment) {
       super(environment);
       RunProfile runProfile = environment.getRunProfile();
       configuration = (BlazeCommandRunConfiguration) runProfile;
diff --git a/base/src/com/google/idea/blaze/base/run/producers/BlazeBuildFileRunConfigurationProducer.java b/base/src/com/google/idea/blaze/base/run/producers/BlazeBuildFileRunConfigurationProducer.java
index aa00460..c8d0ba3 100644
--- a/base/src/com/google/idea/blaze/base/run/producers/BlazeBuildFileRunConfigurationProducer.java
+++ b/base/src/com/google/idea/blaze/base/run/producers/BlazeBuildFileRunConfigurationProducer.java
@@ -19,6 +19,7 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
 import com.google.idea.blaze.base.lang.buildfile.references.BuildReferenceManager;
 import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
 import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
@@ -43,6 +44,7 @@
       Logger.getInstance(BlazeBuildFileRunConfigurationProducer.class);
 
   private static class BuildTarget {
+
     private final FuncallExpression rule;
     private final String ruleType;
     private final Label label;
@@ -111,7 +113,7 @@
 
     // TODO This check should be removed once isTestRule is in a RuleFactory and
     // test rules' suggestedName is modified to account for test filter flags.
-    if (isTestRule(target.ruleType)) {
+    if (Kind.isTestRule(target.ruleType)) {
       BlazeCommandRunConfigurationCommonState handlerState =
           configuration.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
       if (handlerState != null && handlerState.getTestFilterFlag() != null) {
@@ -194,17 +196,8 @@
     if (handlerState != null) {
       // TODO move the old test rule functionality to a BlazeRunConfigurationFactory
       handlerState.setCommand(
-          isTestRule(target.ruleType) ? BlazeCommandName.TEST : BlazeCommandName.BUILD);
+          Kind.isTestRule(target.ruleType) ? BlazeCommandName.TEST : BlazeCommandName.BUILD);
     }
     configuration.setGeneratedName();
   }
-
-  // TODO this functionality should be moved to a BlazeRunConfigurationFactory
-  private static boolean isTestRule(String ruleType) {
-    return isTestSuite(ruleType) || ruleType.endsWith("_test");
-  }
-
-  private static boolean isTestSuite(String ruleType) {
-    return "test_suite".equals(ruleType);
-  }
 }
diff --git a/base/src/com/google/idea/blaze/base/run/rulefinder/RuleFinder.java b/base/src/com/google/idea/blaze/base/run/rulefinder/RuleFinder.java
deleted file mode 100644
index 1e48ec0..0000000
--- a/base/src/com/google/idea/blaze/base/run/rulefinder/RuleFinder.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2016 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.idea.blaze.base.run.rulefinder;
-
-import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.model.primitives.Kind;
-import com.google.idea.blaze.base.model.primitives.Label;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.project.Project;
-import java.util.Arrays;
-import java.util.List;
-import javax.annotation.Nullable;
-
-/** Searches BlazeProjectData for matching rules. */
-public abstract class RuleFinder {
-  public static RuleFinder getInstance() {
-    return ServiceManager.getService(RuleFinder.class);
-  }
-
-  @Nullable
-  public RuleIdeInfo ruleForTarget(Project project, final Label target) {
-    return findRule(project, rule -> rule.label.equals(target));
-  }
-
-  public ImmutableList<RuleIdeInfo> rulesOfKinds(Project project, final Kind... kinds) {
-    return rulesOfKinds(project, Arrays.asList(kinds));
-  }
-
-  public ImmutableList<RuleIdeInfo> rulesOfKinds(Project project, final List<Kind> kinds) {
-    return ImmutableList.copyOf(findRules(project, rule -> rule.kindIsOneOf(kinds)));
-  }
-
-  @Nullable
-  public RuleIdeInfo firstRuleOfKinds(Project project, Kind... kinds) {
-    return Iterables.getFirst(rulesOfKinds(project, kinds), null);
-  }
-
-  @Nullable
-  public RuleIdeInfo firstRuleOfKinds(Project project, List<Kind> kinds) {
-    return Iterables.getFirst(rulesOfKinds(project, kinds), null);
-  }
-
-  @Nullable
-  private RuleIdeInfo findRule(Project project, Predicate<RuleIdeInfo> predicate) {
-    List<RuleIdeInfo> results = findRules(project, predicate);
-    assert results.size() <= 1;
-    return Iterables.getFirst(results, null);
-  }
-
-  @Nullable
-  public RuleIdeInfo findFirstRule(Project project, Predicate<RuleIdeInfo> predicate) {
-    return Iterables.getFirst(findRules(project, predicate), null);
-  }
-
-  public abstract List<RuleIdeInfo> findRules(Project project, Predicate<RuleIdeInfo> predicate);
-}
diff --git a/base/src/com/google/idea/blaze/base/run/targetfinder/TargetFinder.java b/base/src/com/google/idea/blaze/base/run/targetfinder/TargetFinder.java
new file mode 100644
index 0000000..cc88533
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/run/targetfinder/TargetFinder.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.run.targetfinder;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.model.primitives.Kind;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import java.util.Arrays;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Searches BlazeProjectData for matching rules. */
+public abstract class TargetFinder {
+  public static TargetFinder getInstance() {
+    return ServiceManager.getService(TargetFinder.class);
+  }
+
+  @Nullable
+  public TargetIdeInfo targetForLabel(Project project, final Label label) {
+    return findTarget(project, target -> target.key.label.equals(label));
+  }
+
+  public ImmutableList<TargetIdeInfo> targetsOfKinds(Project project, final Kind... kinds) {
+    return targetsOfKinds(project, Arrays.asList(kinds));
+  }
+
+  public ImmutableList<TargetIdeInfo> targetsOfKinds(Project project, final List<Kind> kinds) {
+    return ImmutableList.copyOf(findTargets(project, target -> target.kindIsOneOf(kinds)));
+  }
+
+  @Nullable
+  public TargetIdeInfo firstTargetOfKinds(Project project, Kind... kinds) {
+    return Iterables.getFirst(targetsOfKinds(project, kinds), null);
+  }
+
+  @Nullable
+  public TargetIdeInfo firstTargetOfKinds(Project project, List<Kind> kinds) {
+    return Iterables.getFirst(targetsOfKinds(project, kinds), null);
+  }
+
+  @Nullable
+  private TargetIdeInfo findTarget(Project project, Predicate<TargetIdeInfo> predicate) {
+    List<TargetIdeInfo> results = findTargets(project, predicate);
+    assert results.size() <= 1;
+    return Iterables.getFirst(results, null);
+  }
+
+  @Nullable
+  public TargetIdeInfo findFirstTarget(Project project, Predicate<TargetIdeInfo> predicate) {
+    return Iterables.getFirst(findTargets(project, predicate), null);
+  }
+
+  public abstract List<TargetIdeInfo> findTargets(
+      Project project, Predicate<TargetIdeInfo> predicate);
+}
diff --git a/base/src/com/google/idea/blaze/base/run/rulefinder/RuleFinderImpl.java b/base/src/com/google/idea/blaze/base/run/targetfinder/TargetFinderImpl.java
similarity index 71%
rename from base/src/com/google/idea/blaze/base/run/rulefinder/RuleFinderImpl.java
rename to base/src/com/google/idea/blaze/base/run/targetfinder/TargetFinderImpl.java
index bc547d7..46f58c2 100644
--- a/base/src/com/google/idea/blaze/base/run/rulefinder/RuleFinderImpl.java
+++ b/base/src/com/google/idea/blaze/base/run/targetfinder/TargetFinderImpl.java
@@ -13,11 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.google.idea.blaze.base.run.rulefinder;
+package com.google.idea.blaze.base.run.targetfinder;
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
 import com.intellij.openapi.project.Project;
@@ -25,20 +25,20 @@
 import org.jetbrains.annotations.NotNull;
 
 /** Implementation of RuleFinder. */
-class RuleFinderImpl extends RuleFinder {
+class TargetFinderImpl extends TargetFinder {
   @Override
-  public List<RuleIdeInfo> findRules(
-      @NotNull Project project, @NotNull Predicate<RuleIdeInfo> predicate) {
+  public List<TargetIdeInfo> findTargets(
+      @NotNull Project project, @NotNull Predicate<TargetIdeInfo> predicate) {
     BlazeProjectData projectData =
         BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
     if (projectData == null) {
       return ImmutableList.of();
     }
 
-    ImmutableList.Builder<RuleIdeInfo> resultList = ImmutableList.builder();
-    for (RuleIdeInfo rule : projectData.ruleMap.rules()) {
-      if (predicate.apply(rule)) {
-        resultList.add(rule);
+    ImmutableList.Builder<TargetIdeInfo> resultList = ImmutableList.builder();
+    for (TargetIdeInfo target : projectData.targetMap.targets()) {
+      if (predicate.apply(target)) {
+        resultList.add(target);
       }
     }
     return resultList.build();
diff --git a/base/src/com/google/idea/blaze/base/run/testmap/TestRuleFinderImpl.java b/base/src/com/google/idea/blaze/base/run/testmap/TestTargetFilterImpl.java
similarity index 61%
rename from base/src/com/google/idea/blaze/base/run/testmap/TestRuleFinderImpl.java
rename to base/src/com/google/idea/blaze/base/run/testmap/TestTargetFilterImpl.java
index 9f51c32..c0d9f1a 100644
--- a/base/src/com/google/idea/blaze/base/run/testmap/TestRuleFinderImpl.java
+++ b/base/src/com/google/idea/blaze/base/run/testmap/TestTargetFilterImpl.java
@@ -24,14 +24,14 @@
 import com.google.common.collect.Queues;
 import com.google.common.collect.Sets;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.run.TestRuleFinder;
+import com.google.idea.blaze.base.run.TestTargetFinder;
 import com.google.idea.blaze.base.scope.BlazeContext;
 import com.google.idea.blaze.base.settings.BlazeImportSettings;
 import com.google.idea.blaze.base.sync.SyncListener;
@@ -51,97 +51,98 @@
  *
  * <p>It's essentially a map from source file -> reachable test rules.
  */
-public class TestRuleFinderImpl implements TestRuleFinder {
+public class TestTargetFilterImpl implements TestTargetFinder {
 
   private final Project project;
   @Nullable private TestMap testMap;
 
   static class TestMap {
     private final Project project;
-    private final Multimap<File, RuleKey> rootsMap;
-    private final RuleMap ruleMap;
+    private final Multimap<File, TargetKey> rootsMap;
+    private final TargetMap targetMap;
 
-    TestMap(Project project, ArtifactLocationDecoder artifactLocationDecoder, RuleMap ruleMap) {
+    TestMap(Project project, ArtifactLocationDecoder artifactLocationDecoder, TargetMap targetMap) {
       this.project = project;
-      this.rootsMap = createRootsMap(artifactLocationDecoder, ruleMap.rules());
-      this.ruleMap = ruleMap;
+      this.rootsMap = createRootsMap(artifactLocationDecoder, targetMap.targets());
+      this.targetMap = targetMap;
     }
 
-    private Collection<RuleIdeInfo> testTargetsForSourceFile(File sourceFile) {
+    private Collection<TargetIdeInfo> testTargetsForSourceFile(File sourceFile) {
       BlazeProjectData blazeProjectData =
           BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
       if (blazeProjectData != null) {
-        return testRulesForSourceFile(blazeProjectData.reverseDependencies, sourceFile);
+        return testTargetsForSourceFileImpl(blazeProjectData.reverseDependencies, sourceFile);
       }
       return ImmutableList.of();
     }
 
     @VisibleForTesting
     Collection<Label> testTargetsForSourceFile(
-        ImmutableMultimap<RuleKey, RuleKey> rdepsMap, File sourceFile) {
-      return testRulesForSourceFile(rdepsMap, sourceFile)
+        ImmutableMultimap<TargetKey, TargetKey> rdepsMap, File sourceFile) {
+      return testTargetsForSourceFileImpl(rdepsMap, sourceFile)
           .stream()
-          .filter(RuleIdeInfo::isPlainTarget)
-          .map(rule -> rule.label)
+          .filter(TargetIdeInfo::isPlainTarget)
+          .map(target -> target.key.label)
           .collect(Collectors.toList());
     }
 
-    Collection<RuleIdeInfo> testRulesForSourceFile(
-        ImmutableMultimap<RuleKey, RuleKey> rdepsMap, File sourceFile) {
-      List<RuleIdeInfo> result = Lists.newArrayList();
-      Collection<RuleKey> roots = rootsMap.get(sourceFile);
+    Collection<TargetIdeInfo> testTargetsForSourceFileImpl(
+        ImmutableMultimap<TargetKey, TargetKey> rdepsMap, File sourceFile) {
+      List<TargetIdeInfo> result = Lists.newArrayList();
+      Collection<TargetKey> roots = rootsMap.get(sourceFile);
 
-      Queue<RuleKey> todo = Queues.newArrayDeque();
-      for (RuleKey label : roots) {
+      Queue<TargetKey> todo = Queues.newArrayDeque();
+      for (TargetKey label : roots) {
         todo.add(label);
       }
-      Set<RuleKey> seen = Sets.newHashSet();
+      Set<TargetKey> seen = Sets.newHashSet();
       while (!todo.isEmpty()) {
-        RuleKey ruleKey = todo.remove();
-        if (!seen.add(ruleKey)) {
+        TargetKey targetKey = todo.remove();
+        if (!seen.add(targetKey)) {
           continue;
         }
 
-        RuleIdeInfo rule = ruleMap.get(ruleKey);
-        if (isTestRule(rule)) {
-          result.add(rule);
+        TargetIdeInfo target = targetMap.get(targetKey);
+        if (isTestTarget(target)) {
+          result.add(target);
         }
-        for (RuleKey rdep : rdepsMap.get(ruleKey)) {
+        for (TargetKey rdep : rdepsMap.get(targetKey)) {
           todo.add(rdep);
         }
       }
       return result;
     }
 
-    static Multimap<File, RuleKey> createRootsMap(
-        ArtifactLocationDecoder artifactLocationDecoder, Collection<RuleIdeInfo> rules) {
-      Multimap<File, RuleKey> result = ArrayListMultimap.create();
-      for (RuleIdeInfo ruleIdeInfo : rules) {
-        for (ArtifactLocation source : ruleIdeInfo.sources) {
-          result.put(artifactLocationDecoder.decode(source), ruleIdeInfo.key);
+    static Multimap<File, TargetKey> createRootsMap(
+        ArtifactLocationDecoder artifactLocationDecoder, Collection<TargetIdeInfo> targets) {
+      Multimap<File, TargetKey> result = ArrayListMultimap.create();
+      for (TargetIdeInfo target : targets) {
+        for (ArtifactLocation source : target.sources) {
+          result.put(artifactLocationDecoder.decode(source), target.key);
         }
       }
       return result;
     }
 
-    private static boolean isTestRule(@Nullable RuleIdeInfo rule) {
-      return rule != null
-          && rule.kind != null
-          && rule.kind.isOneOf(
+    private static boolean isTestTarget(@Nullable TargetIdeInfo target) {
+      return target != null
+          && target.kind != null
+          && target.kind.isOneOf(
               Kind.ANDROID_ROBOLECTRIC_TEST,
               Kind.ANDROID_TEST,
               Kind.JAVA_TEST,
               Kind.GWT_TEST,
-              Kind.CC_TEST);
+              Kind.CC_TEST,
+              Kind.PY_TEST);
     }
   }
 
-  public TestRuleFinderImpl(Project project) {
+  public TestTargetFilterImpl(Project project) {
     this.project = project;
   }
 
   @Override
-  public Collection<RuleIdeInfo> testTargetsForSourceFile(File sourceFile) {
+  public Collection<TargetIdeInfo> testTargetsForSourceFile(File sourceFile) {
     TestMap testMap = getTestMap();
     if (testMap == null) {
       return ImmutableList.of();
@@ -163,7 +164,8 @@
     if (blazeProjectData == null) {
       return null;
     }
-    return new TestMap(project, blazeProjectData.artifactLocationDecoder, blazeProjectData.ruleMap);
+    return new TestMap(
+        project, blazeProjectData.artifactLocationDecoder, blazeProjectData.targetMap);
   }
 
   private synchronized void clearMapData() {
@@ -179,8 +181,8 @@
         ProjectViewSet projectViewSet,
         BlazeProjectData blazeProjectData,
         SyncResult syncResult) {
-      TestRuleFinder testRuleFinder = TestRuleFinder.getInstance(project);
-      ((TestRuleFinderImpl) testRuleFinder).clearMapData();
+      TestTargetFinder testTargetFinder = TestTargetFinder.getInstance(project);
+      ((TestTargetFilterImpl) testTargetFinder).clearMapData();
     }
   }
 }
diff --git a/base/src/com/google/idea/blaze/base/settings/ui/BlazeUserSettingsConfigurable.java b/base/src/com/google/idea/blaze/base/settings/ui/BlazeUserSettingsConfigurable.java
index 3b09467..82e45fa 100644
--- a/base/src/com/google/idea/blaze/base/settings/ui/BlazeUserSettingsConfigurable.java
+++ b/base/src/com/google/idea/blaze/base/settings/ui/BlazeUserSettingsConfigurable.java
@@ -18,6 +18,7 @@
 import com.google.common.base.Objects;
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.bazel.BuildSystemProvider;
 import com.google.idea.blaze.base.settings.Blaze;
 import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
 import com.google.idea.blaze.base.settings.BlazeUserSettings;
@@ -25,7 +26,6 @@
 import com.intellij.openapi.options.BaseConfigurable;
 import com.intellij.openapi.options.ConfigurationException;
 import com.intellij.openapi.options.SearchableConfigurable;
-import com.intellij.openapi.project.Project;
 import com.intellij.uiDesigner.core.GridConstraints;
 import com.intellij.uiDesigner.core.GridLayoutManager;
 import com.intellij.uiDesigner.core.Spacer;
@@ -45,7 +45,7 @@
   private static final String BLAZE_BINARY_PATH_KEY = "blaze.binary.path";
   public static final String BAZEL_BINARY_PATH_KEY = "bazel.binary.path";
 
-  private final BuildSystem buildSystem;
+  private final BuildSystem defaultBuildSystem;
   private final Collection<BlazeUserSettingsContributor> settingsContributors;
 
   private JPanel myMainPanel;
@@ -55,8 +55,8 @@
   private FileSelectorWithStoredHistory blazeBinaryPathField;
   private FileSelectorWithStoredHistory bazelBinaryPathField;
 
-  public BlazeUserSettingsConfigurable(Project project) {
-    this.buildSystem = Blaze.getBuildSystem(project);
+  public BlazeUserSettingsConfigurable() {
+    this.defaultBuildSystem = Blaze.defaultBuildSystem();
     this.settingsContributors = Lists.newArrayList();
     for (BlazeUserSettingsContributor.Provider provider :
         BlazeUserSettingsContributor.Provider.EP_NAME.getExtensions()) {
@@ -68,7 +68,7 @@
 
   @Override
   public String getDisplayName() {
-    return buildSystem.getName() + " View Settings";
+    return defaultBuildSystem.getName() + " Settings";
   }
 
   @Nullable
@@ -153,14 +153,14 @@
       contributorRowCount += contributor.getRowCount();
     }
 
-    final int totalRowSize = 5 + contributorRowCount;
+    final int totalRowSize = 6 + contributorRowCount;
     int rowi = 0;
 
     myMainPanel = new JPanel();
     myMainPanel.setLayout(new GridLayoutManager(totalRowSize, 2, new Insets(0, 0, 0, 0), -1, -1));
     suppressConsoleForRunAction = new JCheckBox();
     suppressConsoleForRunAction.setText(
-        String.format("Suppress %s console for Run/Debug actions", buildSystem));
+        String.format("Suppress %s console for Run/Debug actions", defaultBuildSystem));
     suppressConsoleForRunAction.setVerticalAlignment(SwingConstants.CENTER);
     myMainPanel.add(
         suppressConsoleForRunAction,
@@ -227,20 +227,37 @@
         FileSelectorWithStoredHistory.create(
             BAZEL_BINARY_PATH_KEY, "Specify the bazel binary path");
 
-    JLabel pathLabel;
-    JComponent pathPanel;
-    if (buildSystem == BuildSystem.Blaze) {
-      pathPanel = blazeBinaryPathField;
-      pathLabel = new JLabel("Blaze binary location");
-    } else {
-      pathPanel = bazelBinaryPathField;
-      pathLabel = new JLabel("Bazel binary location");
+    if (BuildSystemProvider.isBuildSystemAvailable(BuildSystem.Blaze)) {
+      addBinaryLocationSetting(new JLabel("Blaze binary location"), blazeBinaryPathField, rowi++);
     }
+    if (BuildSystemProvider.isBuildSystemAvailable(BuildSystem.Bazel)) {
+      addBinaryLocationSetting(new JLabel("Bazel binary location"), bazelBinaryPathField, rowi++);
+    }
+
+    myMainPanel.add(
+        new Spacer(),
+        new GridConstraints(
+            rowi,
+            0,
+            1,
+            2,
+            GridConstraints.ANCHOR_CENTER,
+            GridConstraints.FILL_VERTICAL,
+            1,
+            GridConstraints.SIZEPOLICY_WANT_GROW,
+            null,
+            null,
+            null,
+            0,
+            false));
+  }
+
+  private void addBinaryLocationSetting(JLabel pathLabel, JComponent pathPanel, int rowIndex) {
     pathLabel.setLabelFor(pathPanel);
     myMainPanel.add(
         pathLabel,
         new GridConstraints(
-            rowi,
+            rowIndex,
             0,
             1,
             1,
@@ -256,7 +273,7 @@
     myMainPanel.add(
         pathPanel,
         new GridConstraints(
-            rowi,
+            rowIndex,
             1,
             1,
             1,
@@ -269,23 +286,5 @@
             null,
             0,
             false));
-    rowi++;
-
-    myMainPanel.add(
-        new Spacer(),
-        new GridConstraints(
-            rowi,
-            0,
-            1,
-            2,
-            GridConstraints.ANCHOR_CENTER,
-            GridConstraints.FILL_VERTICAL,
-            1,
-            GridConstraints.SIZEPOLICY_WANT_GROW,
-            null,
-            null,
-            null,
-            0,
-            false));
   }
 }
diff --git a/base/src/com/google/idea/blaze/base/settings/ui/ProjectViewUi.java b/base/src/com/google/idea/blaze/base/settings/ui/ProjectViewUi.java
index 85df043..5023368 100644
--- a/base/src/com/google/idea/blaze/base/settings/ui/ProjectViewUi.java
+++ b/base/src/com/google/idea/blaze/base/settings/ui/ProjectViewUi.java
@@ -18,7 +18,6 @@
 import com.google.idea.blaze.base.lang.projectview.language.ProjectViewFileType;
 import com.google.idea.blaze.base.lang.projectview.language.ProjectViewLanguage;
 import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
 import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
 import com.google.idea.blaze.base.scope.OutputSink;
@@ -26,7 +25,7 @@
 import com.google.idea.blaze.base.scope.output.IssueOutput;
 import com.google.idea.blaze.base.settings.Blaze;
 import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
 import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverProvider;
 import com.google.idea.blaze.base.ui.UiUtil;
 import com.intellij.ide.DataManager;
@@ -53,7 +52,6 @@
 import com.intellij.ui.components.JBLabel;
 import java.awt.Dimension;
 import java.awt.KeyboardFocusManager;
-import java.io.File;
 import java.util.List;
 import javax.annotation.Nullable;
 import javax.swing.JCheckBox;
@@ -71,7 +69,7 @@
   private EditorEx projectViewEditor;
   private JCheckBox useShared;
 
-  private WorkspaceRoot workspaceRoot;
+  private WorkspacePathResolver workspacePathResolver;
   private boolean useSharedProjectView;
   private boolean allowEditShared;
   private String sharedProjectViewText;
@@ -168,22 +166,22 @@
   }
 
   public void init(
-      WorkspaceRoot workspaceRoot,
+      WorkspacePathResolver workspacePathResolver,
       String projectViewText,
       @Nullable String sharedProjectViewText,
-      @Nullable File sharedProjectViewFile,
+      @Nullable WorkspacePath sharedProjectViewWorkspacePath,
       boolean useSharedProjectView,
       boolean allowEditShared) {
-    this.workspaceRoot = workspaceRoot;
+    this.workspacePathResolver = workspacePathResolver;
     this.useSharedProjectView = useSharedProjectView;
     this.allowEditShared = allowEditShared;
     this.sharedProjectViewText = sharedProjectViewText;
 
     assert !(useSharedProjectView && sharedProjectViewText == null);
 
-    if (sharedProjectViewFile != null) {
-      WorkspacePath workspacePath = workspaceRoot.workspacePathFor(sharedProjectViewFile);
-      useShared.setText(USE_SHARED_PROJECT_VIEW + ": " + workspacePath.relativePath());
+    if (sharedProjectViewWorkspacePath != null) {
+      useShared.setText(
+          USE_SHARED_PROJECT_VIEW + ": " + sharedProjectViewWorkspacePath.relativePath());
     }
 
     useShared.setSelected(useSharedProjectView);
@@ -192,19 +190,18 @@
       useShared.setEnabled(false);
     }
 
-    setDummyWorkspacePathResolverProvider(workspaceRoot);
+    setDummyWorkspacePathResolverProvider(this.workspacePathResolver);
     setProjectViewText(projectViewText);
     settingsInitialized = true;
   }
 
-  private void setDummyWorkspacePathResolverProvider(WorkspaceRoot workspaceRoot) {
+  private void setDummyWorkspacePathResolverProvider(WorkspacePathResolver workspacePathResolver) {
     MutablePicoContainer container = (MutablePicoContainer) getProject().getPicoContainer();
     Class<WorkspacePathResolverProvider> key = WorkspacePathResolverProvider.class;
     Object oldProvider = container.getComponentInstance(key);
     container.unregisterComponent(key.getName());
     container.registerComponentInstance(
-        key.getName(),
-        (WorkspacePathResolverProvider) () -> new WorkspacePathResolverImpl(workspaceRoot));
+        key.getName(), (WorkspacePathResolverProvider) () -> workspacePathResolver);
     if (!settingsInitialized) {
       Disposer.register(
           parentDisposable,
@@ -246,7 +243,7 @@
         context -> {
           context.addOutputSink(IssueOutput.class, issueCollector);
           ProjectViewParser projectViewParser =
-              new ProjectViewParser(context, new WorkspacePathResolverImpl(workspaceRoot));
+              new ProjectViewParser(context, workspacePathResolver);
           projectViewParser.parseProjectView(projectViewText);
           return projectViewParser.getResult();
         });
diff --git a/base/src/com/google/idea/blaze/base/sync/BlazeSyncParams.java b/base/src/com/google/idea/blaze/base/sync/BlazeSyncParams.java
index 0f904fc..2ee84c7 100644
--- a/base/src/com/google/idea/blaze/base/sync/BlazeSyncParams.java
+++ b/base/src/com/google/idea/blaze/base/sync/BlazeSyncParams.java
@@ -26,8 +26,13 @@
 
   /** The kind of sync. */
   public enum SyncMode {
+    /** Happens on startup, restores in-memory state */
     RESTORE_EPHEMERAL_STATE,
+    /** Partial / working set sync */
+    PARTIAL,
+    /** This is the standard incremental sync */
     INCREMENTAL,
+    /** Full sync, can invalidate/redo work that an incremental sync does not */
     FULL
   }
 
diff --git a/base/src/com/google/idea/blaze/base/sync/BlazeSyncPlugin.java b/base/src/com/google/idea/blaze/base/sync/BlazeSyncPlugin.java
index f2639cf..b29ce17 100644
--- a/base/src/com/google/idea/blaze/base/sync/BlazeSyncPlugin.java
+++ b/base/src/com/google/idea/blaze/base/sync/BlazeSyncPlugin.java
@@ -17,7 +17,7 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.SyncState;
 import com.google.idea.blaze.base.model.primitives.LanguageClass;
@@ -26,6 +26,7 @@
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
 import com.google.idea.blaze.base.projectview.section.SectionParser;
 import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.sync.libraries.LibrarySource;
 import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
 import com.google.idea.blaze.base.sync.workspace.BlazeRoots;
@@ -79,6 +80,12 @@
     void commit();
   }
 
+  /**
+   * The {@link WorkspaceType}s supported by this plugin. Not used to choose the project's
+   * WorkspaceType.
+   */
+  ImmutableList<WorkspaceType> getSupportedWorkspaceTypes();
+
   /** @return The default workspace type recommended by this plugin. */
   @Nullable
   WorkspaceType getDefaultWorkspaceType();
@@ -104,7 +111,7 @@
       @Nullable WorkingSet workingSet,
       WorkspacePathResolver workspacePathResolver,
       ArtifactLocationDecoder artifactLocationDecoder,
-      RuleMap ruleMap,
+      TargetMap targetMap,
       SyncState.Builder syncStateBuilder,
       @Nullable SyncState previousSyncState);
 
@@ -155,9 +162,17 @@
   /** Returns any custom sections that this plugin supports. */
   Collection<SectionParser> getSections();
 
+  @Nullable
+  LibrarySource getLibrarySource(BlazeProjectData blazeProjectData);
+
   /** Convenience adapter to help stubbing out methods. */
   class Adapter implements BlazeSyncPlugin {
 
+    @Override
+    public ImmutableList<WorkspaceType> getSupportedWorkspaceTypes() {
+      return ImmutableList.of();
+    }
+
     @Nullable
     @Override
     public WorkspaceType getDefaultWorkspaceType() {
@@ -189,7 +204,7 @@
         @Nullable WorkingSet workingSet,
         WorkspacePathResolver workspacePathResolver,
         ArtifactLocationDecoder artifactLocationDecoder,
-        RuleMap ruleMap,
+        TargetMap targetMap,
         SyncState.Builder syncStateBuilder,
         @Nullable SyncState previousSyncState) {}
 
@@ -240,5 +255,10 @@
       return ImmutableList.of();
     }
 
+    @Nullable
+    @Override
+    public LibrarySource getLibrarySource(BlazeProjectData blazeProjectData) {
+      return null;
+    }
   }
 }
diff --git a/base/src/com/google/idea/blaze/base/sync/BlazeSyncTask.java b/base/src/com/google/idea/blaze/base/sync/BlazeSyncTask.java
old mode 100755
new mode 100644
index d8d3642..68ee51d
--- a/base/src/com/google/idea/blaze/base/sync/BlazeSyncTask.java
+++ b/base/src/com/google/idea/blaze/base/sync/BlazeSyncTask.java
@@ -16,6 +16,8 @@
 package com.google.idea.blaze.base.sync;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -25,11 +27,13 @@
 import com.google.idea.blaze.base.async.AsyncUtil;
 import com.google.idea.blaze.base.async.FutureUtil;
 import com.google.idea.blaze.base.async.executor.BlazeExecutor;
+import com.google.idea.blaze.base.command.info.BlazeInfo;
 import com.google.idea.blaze.base.experiments.ExperimentScope;
 import com.google.idea.blaze.base.filecache.FileCaches;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import com.google.idea.blaze.base.metrics.Action;
+import com.google.idea.blaze.base.model.BlazeLibrary;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.SyncState;
 import com.google.idea.blaze.base.model.primitives.TargetExpression;
@@ -40,7 +44,6 @@
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
 import com.google.idea.blaze.base.projectview.ProjectViewVerifier;
 import com.google.idea.blaze.base.projectview.section.sections.TargetSection;
-import com.google.idea.blaze.base.rulemaps.ReverseDependencyMap;
 import com.google.idea.blaze.base.scope.BlazeContext;
 import com.google.idea.blaze.base.scope.Scope;
 import com.google.idea.blaze.base.scope.output.IssueOutput;
@@ -64,6 +67,8 @@
 import com.google.idea.blaze.base.sync.aspects.BlazeIdeInterface.BuildResult;
 import com.google.idea.blaze.base.sync.data.BlazeDataStorage;
 import com.google.idea.blaze.base.sync.data.BlazeProjectDataManagerImpl;
+import com.google.idea.blaze.base.sync.libraries.BlazeLibraryCollector;
+import com.google.idea.blaze.base.sync.libraries.LibraryEditor;
 import com.google.idea.blaze.base.sync.projectstructure.ContentEntryEditor;
 import com.google.idea.blaze.base.sync.projectstructure.ModuleEditorImpl;
 import com.google.idea.blaze.base.sync.projectstructure.ModuleEditorProvider;
@@ -76,6 +81,7 @@
 import com.google.idea.blaze.base.sync.workspace.WorkingSet;
 import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
 import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl;
+import com.google.idea.blaze.base.targetmaps.ReverseDependencyMap;
 import com.google.idea.blaze.base.util.SaveUtil;
 import com.google.idea.blaze.base.vcs.BlazeVcsHandler;
 import com.intellij.openapi.diagnostic.Logger;
@@ -184,7 +190,7 @@
 
     BlazeVcsHandler vcsHandler = null;
     for (BlazeVcsHandler candidate : BlazeVcsHandler.EP_NAME.getExtensions()) {
-      if (candidate.handlesProject(Blaze.getBuildSystem(project), workspaceRoot)) {
+      if (candidate.handlesProject(importSettings.getBuildSystem(), workspaceRoot)) {
         vcsHandler = candidate;
         break;
       }
@@ -194,23 +200,28 @@
       return SyncResult.FAILURE;
     }
 
+    ListenableFuture<ImmutableMap<String, String>> blazeInfoFuture =
+        BlazeInfo.getInstance()
+            .runBlazeInfo(
+                context, importSettings.getBuildSystem(), workspaceRoot, ImmutableList.of());
+
     ListeningExecutorService executor = BlazeExecutor.getInstance().getExecutor();
-    ListenableFuture<BlazeRoots> blazeRootsFuture =
-        BlazeRoots.compute(project, workspaceRoot, context);
     ListenableFuture<WorkingSet> workingSetFuture =
         vcsHandler.getWorkingSet(project, context, workspaceRoot, executor);
 
-    BlazeRoots blazeRoots =
-        FutureUtil.waitForFuture(context, blazeRootsFuture)
-            .timed(Blaze.buildSystemName(project) + "Roots")
+    ImmutableMap<String, String> blazeInfo =
+        FutureUtil.waitForFuture(context, blazeInfoFuture)
+            .timed(Blaze.buildSystemName(project) + "Info")
             .withProgressMessage(
                 String.format("Running %s info...", Blaze.buildSystemName(project)))
-            .onError(String.format("Could not get %s roots", Blaze.buildSystemName(project)))
+            .onError(String.format("Could not run %s info", Blaze.buildSystemName(project)))
             .run()
             .result();
-    if (blazeRoots == null) {
+    if (blazeInfo == null) {
       return SyncResult.FAILURE;
     }
+    BlazeRoots blazeRoots =
+        BlazeRoots.build(importSettings.getBuildSystem(), workspaceRoot, blazeInfo);
 
     WorkspacePathResolverAndProjectView workspacePathResolverAndProjectView =
         computeWorkspacePathResolverAndProjectView(context, blazeRoots, vcsHandler, executor);
@@ -267,12 +278,25 @@
 
       List<TargetExpression> targets = Lists.newArrayList();
       if (syncParams.addProjectViewTargets || oldBlazeProjectData == null) {
-        targets.addAll(projectViewSet.listItems(TargetSection.KEY));
+        Collection<TargetExpression> projectViewTargets =
+            projectViewSet.listItems(TargetSection.KEY);
+        if (!projectViewTargets.isEmpty()) {
+          targets.addAll(projectViewTargets);
+          printTargets(context, "project view", projectViewTargets);
+        }
       }
       if (syncParams.addWorkingSet && workingSet != null) {
-        targets.addAll(getWorkingSetTargets(projectViewSet, workingSet));
+        Collection<? extends TargetExpression> workingSetTargets =
+            getWorkingSetTargets(projectViewSet, workingSet);
+        if (!workingSetTargets.isEmpty()) {
+          targets.addAll(workingSetTargets);
+          printTargets(context, "working set", workingSetTargets);
+        }
       }
-      targets.addAll(syncParams.targetExpressions);
+      if (!syncParams.targetExpressions.isEmpty()) {
+        targets.addAll(syncParams.targetExpressions);
+        printTargets(context, syncParams.title, syncParams.targetExpressions);
+      }
 
       boolean mergeWithOldState = !syncParams.addProjectViewTargets;
       BlazeIdeInterface.IdeResult ideQueryResult =
@@ -289,16 +313,17 @@
       if (context.isCancelled()) {
         return SyncResult.CANCELLED;
       }
-      if (ideQueryResult.ruleMap == null || ideQueryResult.buildResult == BuildResult.FATAL_ERROR) {
+      if (ideQueryResult.targetMap == null
+          || ideQueryResult.buildResult == BuildResult.FATAL_ERROR) {
         context.setHasError();
         return SyncResult.FAILURE;
       }
 
-      RuleMap ruleMap = ideQueryResult.ruleMap;
+      TargetMap targetMap = ideQueryResult.targetMap;
       ideInfoResult = ideQueryResult.buildResult;
 
-      ListenableFuture<ImmutableMultimap<RuleKey, RuleKey>> reverseDependenciesFuture =
-          BlazeExecutor.getInstance().submit(() -> ReverseDependencyMap.createRdepsMap(ruleMap));
+      ListenableFuture<ImmutableMultimap<TargetKey, TargetKey>> reverseDependenciesFuture =
+          BlazeExecutor.getInstance().submit(() -> ReverseDependencyMap.createRdepsMap(targetMap));
 
       ideResolveResult =
           resolveIdeArtifacts(project, context, workspaceRoot, projectViewSet, targets);
@@ -325,13 +350,13 @@
                   workingSet,
                   workspacePathResolver,
                   artifactLocationDecoder,
-                  ruleMap,
+                  targetMap,
                   syncStateBuilder,
                   previousSyncState);
             }
           });
 
-      ImmutableMultimap<RuleKey, RuleKey> reverseDependencies =
+      ImmutableMultimap<TargetKey, TargetKey> reverseDependencies =
           FutureUtil.waitForFuture(context, reverseDependenciesFuture)
               .timed("ReverseDependencies")
               .onError("Failed to compute reverse dependency map")
@@ -344,7 +369,8 @@
       newBlazeProjectData =
           new BlazeProjectData(
               syncStartTime,
-              ruleMap,
+              targetMap,
+              blazeInfo,
               blazeRoots,
               workingSet,
               workspacePathResolver,
@@ -491,6 +517,17 @@
     if (messages.size() > maxFiles) {
       context.output(PrintOutput.log(String.format("  (and %d more)", messages.size() - maxFiles)));
     }
+    context.output(PrintOutput.output(""));
+  }
+
+  private void printTargets(
+      BlazeContext context, String owner, Collection<? extends TargetExpression> targets) {
+    StringBuilder sb = new StringBuilder("Sync targets from ");
+    sb.append(owner).append(':').append('\n');
+    for (TargetExpression targetExpression : targets) {
+      sb.append("  ").append(targetExpression).append('\n');
+    }
+    context.output(PrintOutput.log(sb.toString()));
   }
 
   private Collection<? extends TargetExpression> getWorkingSetTargets(
@@ -533,7 +570,7 @@
           context.setPropagatesErrors(false);
 
           BlazeIdeInterface blazeIdeInterface = BlazeIdeInterface.getInstance();
-          return blazeIdeInterface.updateRuleMap(
+          return blazeIdeInterface.updateTargetMap(
               project,
               context,
               workspaceRoot,
@@ -655,6 +692,10 @@
         newBlazeProjectData,
         workspaceModifiableModel);
 
+    List<BlazeLibrary> libraries = BlazeLibraryCollector.getLibraries(newBlazeProjectData);
+    LibraryEditor.updateProjectLibraries(project, context, newBlazeProjectData, libraries);
+    LibraryEditor.configureDependencies(workspaceModifiableModel, libraries);
+
     for (BlazeSyncPlugin blazeSyncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
       blazeSyncPlugin.updateProjectStructure(
           project,
diff --git a/base/src/com/google/idea/blaze/base/sync/actions/PartialSyncAction.java b/base/src/com/google/idea/blaze/base/sync/actions/PartialSyncAction.java
index 08010af..e761f11 100644
--- a/base/src/com/google/idea/blaze/base/sync/actions/PartialSyncAction.java
+++ b/base/src/com/google/idea/blaze/base/sync/actions/PartialSyncAction.java
@@ -21,13 +21,13 @@
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.projectview.ProjectViewManager;
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.rulemaps.SourceToRuleMap;
 import com.google.idea.blaze.base.settings.Blaze;
 import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
 import com.google.idea.blaze.base.sync.BlazeSyncManager;
 import com.google.idea.blaze.base.sync.BlazeSyncParams;
 import com.google.idea.blaze.base.sync.BuildTargetFinder;
 import com.google.idea.blaze.base.sync.projectview.ImportRoots;
+import com.google.idea.blaze.base.targetmaps.SourceToTargetMap;
 import com.intellij.openapi.actionSystem.AnActionEvent;
 import com.intellij.openapi.actionSystem.CommonDataKeys;
 import com.intellij.openapi.actionSystem.Presentation;
@@ -47,7 +47,7 @@
       getTargets(e, targetExpressions);
 
       BlazeSyncParams syncParams =
-          new BlazeSyncParams.Builder("Partial Sync", BlazeSyncParams.SyncMode.INCREMENTAL)
+          new BlazeSyncParams.Builder("Partial Sync", BlazeSyncParams.SyncMode.PARTIAL)
               .addTargetExpressions(targetExpressions)
               .build();
 
@@ -86,12 +86,12 @@
     }
 
     WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
-    SourceToRuleMap.getInstance(project);
+    SourceToTargetMap.getInstance(project);
 
     String objectName = virtualFile.isDirectory() ? "Package" : "File";
     if (!virtualFile.isDirectory()) {
       targets.addAll(
-          SourceToRuleMap.getInstance(project)
+          SourceToTargetMap.getInstance(project)
               .getTargetsToBuildForSourceFile(new File(virtualFile.getPath())));
     }
     if (targets.isEmpty()) {
diff --git a/base/src/com/google/idea/blaze/base/sync/actions/SyncWorkingSetAction.java b/base/src/com/google/idea/blaze/base/sync/actions/SyncWorkingSetAction.java
index 530f819..950f473 100644
--- a/base/src/com/google/idea/blaze/base/sync/actions/SyncWorkingSetAction.java
+++ b/base/src/com/google/idea/blaze/base/sync/actions/SyncWorkingSetAction.java
@@ -18,6 +18,7 @@
 import com.google.idea.blaze.base.actions.BlazeAction;
 import com.google.idea.blaze.base.sync.BlazeSyncManager;
 import com.google.idea.blaze.base.sync.BlazeSyncParams;
+import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode;
 import com.intellij.openapi.actionSystem.AnActionEvent;
 import com.intellij.openapi.project.Project;
 
@@ -28,7 +29,7 @@
     Project project = e.getProject();
     if (project != null) {
       BlazeSyncParams syncParams =
-          new BlazeSyncParams.Builder("Sync Working Set", BlazeSyncParams.SyncMode.INCREMENTAL)
+          new BlazeSyncParams.Builder("Sync Working Set", SyncMode.PARTIAL)
               .addWorkingSet(true)
               .build();
 
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/AspectStrategy.java b/base/src/com/google/idea/blaze/base/sync/aspects/AspectStrategy.java
deleted file mode 100644
index 7755abd..0000000
--- a/base/src/com/google/idea/blaze/base/sync/aspects/AspectStrategy.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright 2016 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.idea.blaze.base.sync.aspects;
-
-import com.google.idea.blaze.base.command.BlazeCommand;
-import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.AndroidStudioIdeInfo;
-import com.google.repackaged.protobuf.TextFormat;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-
-/** Indirection for our various ways of calling the aspect. */
-public interface AspectStrategy {
-
-  String getName();
-
-  void modifyIdeInfoCommand(BlazeCommand.Builder blazeCommandBuilder);
-
-  void modifyIdeResolveCommand(BlazeCommand.Builder blazeCommandBuilder);
-
-  String getAspectOutputFileExtension();
-
-  AndroidStudioIdeInfo.RuleIdeInfo readAspectFile(File file) throws IOException;
-
-  AspectStrategy NATIVE_ASPECT =
-      new AspectStrategy() {
-        @Override
-        public String getName() {
-          return "NativeAspect";
-        }
-
-        @Override
-        public void modifyIdeInfoCommand(BlazeCommand.Builder blazeCommandBuilder) {
-          blazeCommandBuilder
-              .addBlazeFlags("--aspects=AndroidStudioInfoAspect")
-              .addBlazeFlags("--output_groups=ide-info");
-        }
-
-        @Override
-        public void modifyIdeResolveCommand(BlazeCommand.Builder blazeCommandBuilder) {
-          blazeCommandBuilder
-              .addBlazeFlags("--aspects=AndroidStudioInfoAspect")
-              .addBlazeFlags("--output_groups=ide-resolve");
-        }
-
-        @Override
-        public String getAspectOutputFileExtension() {
-          return ".aswb-build";
-        }
-
-        @Override
-        public AndroidStudioIdeInfo.RuleIdeInfo readAspectFile(File file) throws IOException {
-          try (InputStream inputStream = new FileInputStream(file)) {
-            return AndroidStudioIdeInfo.RuleIdeInfo.parseFrom(inputStream);
-          }
-        }
-      };
-
-  AspectStrategy SKYLARK_ASPECT =
-      new AspectStrategy() {
-        @Override
-        public String getName() {
-          return "SkylarkAspect";
-        }
-
-        private void addAspectFlag(BlazeCommand.Builder blazeCommandBuilder) {
-          blazeCommandBuilder.addBlazeFlags(
-              "--aspects=//third_party/bazel/src/test/java/com/google/devtools/build/lib/ideinfo/intellij_info.bzl%intellij_info_aspect");
-        }
-
-        @Override
-        public void modifyIdeInfoCommand(BlazeCommand.Builder blazeCommandBuilder) {
-          addAspectFlag(blazeCommandBuilder);
-          blazeCommandBuilder.addBlazeFlags("--output_groups=ide-info-text");
-        }
-
-        @Override
-        public void modifyIdeResolveCommand(BlazeCommand.Builder blazeCommandBuilder) {
-          addAspectFlag(blazeCommandBuilder);
-          blazeCommandBuilder.addBlazeFlags("--output_groups=ide-resolve");
-        }
-
-        @Override
-        public String getAspectOutputFileExtension() {
-          return ".intellij-build.txt";
-        }
-
-        @Override
-        public AndroidStudioIdeInfo.RuleIdeInfo readAspectFile(File file) throws IOException {
-          try (InputStream inputStream = new FileInputStream(file)) {
-            AndroidStudioIdeInfo.RuleIdeInfo.Builder builder =
-                AndroidStudioIdeInfo.RuleIdeInfo.newBuilder();
-            TextFormat.Parser parser =
-                TextFormat.Parser.newBuilder().setAllowUnknownFields(true).build();
-            parser.merge(new InputStreamReader(inputStream), builder);
-            return builder.build();
-          }
-        }
-      };
-}
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterface.java b/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterface.java
index 573c4b8..4051083 100644
--- a/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterface.java
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterface.java
@@ -15,7 +15,7 @@
  */
 package com.google.idea.blaze.base.sync.aspects;
 
-import com.google.idea.blaze.base.ideinfo.RuleMap;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import com.google.idea.blaze.base.model.SyncState;
 import com.google.idea.blaze.base.model.primitives.TargetExpression;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
@@ -53,11 +53,11 @@
 
   /** The result of the ide operation */
   class IdeResult {
-    @Nullable public final RuleMap ruleMap;
+    @Nullable public final TargetMap targetMap;
     public final BuildResult buildResult;
 
-    public IdeResult(@Nullable RuleMap ruleMap, BuildResult buildResult) {
-      this.ruleMap = ruleMap;
+    public IdeResult(@Nullable TargetMap targetMap, BuildResult buildResult) {
+      this.targetMap = targetMap;
       this.buildResult = buildResult;
     }
   }
@@ -68,7 +68,7 @@
    * @param mergeWithOldState If true, we overlay the given targets to the current rule map.
    * @return A tuple of the latest updated rule map and the result of the operation.
    */
-  IdeResult updateRuleMap(
+  IdeResult updateTargetMap(
       Project project,
       BlazeContext context,
       WorkspaceRoot workspaceRoot,
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImpl.java b/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImpl.java
index 936268e..fdc584d 100644
--- a/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImpl.java
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImpl.java
@@ -31,9 +31,9 @@
 import com.google.idea.blaze.base.command.BlazeFlags;
 import com.google.idea.blaze.base.command.ExperimentalShowArtifactsLineProcessor;
 import com.google.idea.blaze.base.filecache.FileDiffer;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
 import com.google.idea.blaze.base.metrics.Action;
 import com.google.idea.blaze.base.model.SyncState;
@@ -51,40 +51,42 @@
 import com.google.idea.blaze.base.scope.scopes.TimingScope;
 import com.google.idea.blaze.base.settings.Blaze;
 import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.google.idea.blaze.base.sync.aspects.strategy.AspectStrategy;
+import com.google.idea.blaze.base.sync.aspects.strategy.AspectStrategyProvider;
 import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
-import com.google.idea.common.experiments.BoolExperiment;
-import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.AndroidStudioIdeInfo;
+import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.io.Serializable;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Predicate;
+import java.util.zip.GZIPInputStream;
 import javax.annotation.Nullable;
 
 /** Implementation of BlazeIdeInterface based on aspects. */
 public class BlazeIdeInterfaceAspectsImpl implements BlazeIdeInterface {
 
   private static final Logger LOG = Logger.getInstance(BlazeIdeInterfaceAspectsImpl.class);
-  private static final BoolExperiment USE_SKYLARK_ASPECT =
-      new BoolExperiment("use.skylark.aspect", false);
-  private static final BoolExperiment IDE_INFO_KEEP_GOING =
-      new BoolExperiment("ide.info.keep.going", true);
 
   static class State implements Serializable {
     private static final long serialVersionUID = 14L;
-    RuleMap ruleMap;
+    TargetMap targetMap;
     ImmutableMap<File, Long> fileState = null;
-    Map<File, RuleKey> fileToRuleMapKey = Maps.newHashMap();
+    Map<File, TargetKey> fileToTargetMapKey = Maps.newHashMap();
     WorkspaceLanguageSettings workspaceLanguageSettings;
     String aspectStrategyName;
   }
 
   @Override
-  public IdeResult updateRuleMap(
+  public IdeResult updateTargetMap(
       Project project,
       BlazeContext context,
       WorkspaceRoot workspaceRoot,
@@ -113,7 +115,7 @@
     IdeInfoResult ideInfoResult =
         getIdeInfo(project, context, workspaceRoot, projectViewSet, targets, aspectStrategy);
     if (ideInfoResult.buildResult == BuildResult.FATAL_ERROR) {
-      return new IdeResult(prevState != null ? prevState.ruleMap : null, BuildResult.FATAL_ERROR);
+      return new IdeResult(prevState != null ? prevState.targetMap : null, BuildResult.FATAL_ERROR);
     }
     // If there was a partial error, make a best-effort attempt to sync. Retain
     // any old state that we have in an attempt not to lose too much code.
@@ -128,7 +130,7 @@
         FileDiffer.updateFiles(
             prevState != null ? prevState.fileState : null, fileList, updatedFiles, removedFiles);
     if (fileState == null) {
-      return new IdeResult(prevState != null ? prevState.ruleMap : null, BuildResult.FATAL_ERROR);
+      return new IdeResult(prevState != null ? prevState.targetMap : null, BuildResult.FATAL_ERROR);
     }
 
     context.output(
@@ -144,7 +146,7 @@
         .withProgressMessage("Reading IDE info result...")
         .run()
         .success()) {
-      return new IdeResult(prevState != null ? prevState.ruleMap : null, BuildResult.FATAL_ERROR);
+      return new IdeResult(prevState != null ? prevState.targetMap : null, BuildResult.FATAL_ERROR);
     }
 
     State state =
@@ -159,10 +161,10 @@
             removedFiles,
             mergeWithOldState);
     if (state == null) {
-      return new IdeResult(prevState != null ? prevState.ruleMap : null, BuildResult.FATAL_ERROR);
+      return new IdeResult(prevState != null ? prevState.targetMap : null, BuildResult.FATAL_ERROR);
     }
     syncStateBuilder.put(State.class, state);
-    return new IdeResult(state.ruleMap, ideInfoResult.buildResult);
+    return new IdeResult(state.targetMap, ideInfoResult.buildResult);
   }
 
   private static class IdeInfoResult {
@@ -194,45 +196,41 @@
           BlazeCommand.Builder blazeCommandBuilder =
               BlazeCommand.builder(buildSystem, BlazeCommandName.BUILD);
           blazeCommandBuilder.addTargets(targets);
-          if (IDE_INFO_KEEP_GOING.getValue()) {
-            blazeCommandBuilder.addBlazeFlags(BlazeFlags.KEEP_GOING);
-          }
+          blazeCommandBuilder.addBlazeFlags(BlazeFlags.KEEP_GOING);
           blazeCommandBuilder
               .addBlazeFlags(BlazeFlags.EXPERIMENTAL_SHOW_ARTIFACTS)
               .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet));
 
           aspectStrategy.modifyIdeInfoCommand(blazeCommandBuilder);
 
+          String fileExtension = aspectStrategy.getAspectOutputFileExtension();
+          String gzFileExtension = fileExtension + ".gz";
+          Predicate<String> fileFilter =
+              fileName -> fileName.endsWith(fileExtension) || fileName.endsWith(gzFileExtension);
+
           int retVal =
               ExternalTask.builder(workspaceRoot)
                   .addBlazeCommand(blazeCommandBuilder.build())
                   .context(context)
                   .stderr(
                       LineProcessingOutputStream.of(
-                          new ExperimentalShowArtifactsLineProcessor(
-                              result, aspectStrategy.getAspectOutputFileExtension()),
+                          new ExperimentalShowArtifactsLineProcessor(result, fileFilter),
                           new IssueOutputLineProcessor(project, context, workspaceRoot)))
                   .build()
                   .run(new LoggedTimingScope(project, Action.BLAZE_BUILD));
 
           BuildResult buildResult = BuildResult.fromExitCode(retVal);
-
-          // If the experiment is turned off, upgrade any build errors to fatal errors
-          if (buildResult == BuildResult.BUILD_ERROR && !IDE_INFO_KEEP_GOING.getValue()) {
-            buildResult = BuildResult.FATAL_ERROR;
-          }
-
           return new IdeInfoResult(result, buildResult);
         });
   }
 
-  private static class RuleIdeInfoPair {
+  private static class TargetFilePair {
     private final File file;
-    private final RuleIdeInfo ruleIdeInfo;
+    private final TargetIdeInfo target;
 
-    RuleIdeInfoPair(File file, RuleIdeInfo ruleIdeInfo) {
+    TargetFilePair(File file, TargetIdeInfo target) {
       this.file = file;
-      this.ruleIdeInfo = ruleIdeInfo;
+      this.target = target;
     }
   }
 
@@ -252,7 +250,7 @@
             parentContext,
             (ScopedFunction<Result<State>>)
                 context -> {
-                  context.push(new TimingScope("UpdateRuleMap"));
+                  context.push(new TimingScope("UpdateTargetMap"));
 
                   // If we're not removing we have to merge the old state
                   // into the new one or we'll miss file removes next time
@@ -273,19 +271,19 @@
                   state.workspaceLanguageSettings = workspaceLanguageSettings;
                   state.aspectStrategyName = aspectStrategy.getName();
 
-                  Map<RuleKey, RuleIdeInfo> ruleMap = Maps.newHashMap();
-                  Map<RuleKey, RuleIdeInfo> updatedRules = Maps.newHashMap();
+                  Map<TargetKey, TargetIdeInfo> targetMap = Maps.newHashMap();
+                  Map<TargetKey, TargetIdeInfo> updatedTargets = Maps.newHashMap();
                   if (prevState != null) {
-                    ruleMap.putAll(prevState.ruleMap.map());
-                    state.fileToRuleMapKey.putAll(prevState.fileToRuleMapKey);
+                    targetMap.putAll(prevState.targetMap.map());
+                    state.fileToTargetMapKey.putAll(prevState.fileToTargetMapKey);
                   }
 
                   // Update removed unless we're merging with the old state
                   if (!mergeWithOldState) {
                     for (File removedFile : removedFiles) {
-                      RuleKey key = state.fileToRuleMapKey.remove(removedFile);
+                      TargetKey key = state.fileToTargetMapKey.remove(removedFile);
                       if (key != null) {
-                        ruleMap.remove(key);
+                        targetMap.remove(key);
                       }
                     }
                   }
@@ -295,35 +293,36 @@
                   ListeningExecutorService executor = BlazeExecutor.getInstance().getExecutor();
 
                   // Read protos from any new files
-                  List<ListenableFuture<RuleIdeInfoPair>> futures = Lists.newArrayList();
+                  List<ListenableFuture<TargetFilePair>> futures = Lists.newArrayList();
                   for (File file : newFiles) {
                     futures.add(
                         executor.submit(
                             () -> {
                               totalSizeLoaded.addAndGet(file.length());
-
-                              AndroidStudioIdeInfo.RuleIdeInfo ruleProto =
-                                  aspectStrategy.readAspectFile(file);
-                              RuleIdeInfo ruleIdeInfo =
-                                  IdeInfoFromProtobuf.makeRuleIdeInfo(
-                                      workspaceLanguageSettings, ruleProto);
-                              return new RuleIdeInfoPair(file, ruleIdeInfo);
+                              try (InputStream inputStream = getAspectInputStream(file)) {
+                                IntellijIdeInfo.TargetIdeInfo ruleProto =
+                                    aspectStrategy.readAspectFile(inputStream);
+                                TargetIdeInfo target =
+                                    IdeInfoFromProtobuf.makeTargetIdeInfo(
+                                        workspaceLanguageSettings, ruleProto);
+                                return new TargetFilePair(file, target);
+                              }
                             }));
                   }
 
                   // Update state with result from proto files
-                  int duplicateRuleLabels = 0;
+                  int duplicateTargetLabels = 0;
                   try {
-                    for (RuleIdeInfoPair ruleIdeInfoOrSdkInfo : Futures.allAsList(futures).get()) {
-                      if (ruleIdeInfoOrSdkInfo.ruleIdeInfo != null) {
-                        File file = ruleIdeInfoOrSdkInfo.file;
-                        RuleKey key = ruleIdeInfoOrSdkInfo.ruleIdeInfo.key;
-                        RuleIdeInfo previousRule =
-                            updatedRules.putIfAbsent(key, ruleIdeInfoOrSdkInfo.ruleIdeInfo);
-                        if (previousRule == null) {
-                          state.fileToRuleMapKey.put(file, key);
+                    for (TargetFilePair targetFilePairs : Futures.allAsList(futures).get()) {
+                      if (targetFilePairs.target != null) {
+                        File file = targetFilePairs.file;
+                        TargetKey key = targetFilePairs.target.key;
+                        TargetIdeInfo previousTarget =
+                            updatedTargets.putIfAbsent(key, targetFilePairs.target);
+                        if (previousTarget == null) {
+                          state.fileToTargetMapKey.put(file, key);
                         } else {
-                          duplicateRuleLabels++;
+                          duplicateTargetLabels++;
                         }
                       }
                     }
@@ -333,25 +332,25 @@
                   } catch (ExecutionException e) {
                     return Result.error(e);
                   }
-                  ruleMap.putAll(updatedRules);
+                  targetMap.putAll(updatedTargets);
 
                   context.output(
                       PrintOutput.log(
                           String.format(
                               "Loaded %d aspect files, total size %dkB",
                               newFiles.size(), totalSizeLoaded.get() / 1024)));
-                  if (duplicateRuleLabels > 0) {
+                  if (duplicateTargetLabels > 0) {
                     context.output(
                         new PerformanceWarning(
                             String.format(
                                 "There were %d duplicate rules. "
                                     + "You may be including multiple configurations in your build. "
                                     + "Your IDE sync is slowed down by ~%d%%.",
-                                duplicateRuleLabels,
-                                (100 * duplicateRuleLabels / ruleMap.size()))));
+                                duplicateTargetLabels,
+                                (100 * duplicateTargetLabels / targetMap.size()))));
                   }
 
-                  state.ruleMap = new RuleMap(ImmutableMap.copyOf(ruleMap));
+                  state.targetMap = new TargetMap(ImmutableMap.copyOf(targetMap));
                   return Result.of(state);
                 });
 
@@ -362,6 +361,14 @@
     return result.result;
   }
 
+  private static InputStream getAspectInputStream(File file) throws IOException {
+    InputStream inputStream = new FileInputStream(file);
+    if (file.getName().endsWith(".gz")) {
+      inputStream = new GZIPInputStream(inputStream);
+    }
+    return inputStream;
+  }
+
   @Override
   public BuildResult resolveIdeArtifacts(
       Project project,
@@ -396,12 +403,13 @@
   }
 
   private AspectStrategy getAspectStrategy(Project project) {
-    BuildSystem buildSystem = Blaze.getBuildSystem(project);
-    if (buildSystem == BuildSystem.Bazel) {
-      return AspectStrategy.NATIVE_ASPECT;
+    for (AspectStrategyProvider provider : AspectStrategyProvider.EP_NAME.getExtensions()) {
+      AspectStrategy aspectStrategy = provider.getAspectStrategy(project);
+      if (aspectStrategy != null) {
+        return aspectStrategy;
+      }
     }
-    return USE_SKYLARK_ASPECT.getValue()
-        ? AspectStrategy.SKYLARK_ASPECT
-        : AspectStrategy.NATIVE_ASPECT;
+    // Should never get here
+    throw new IllegalStateException("No aspect strategy found.");
   }
 }
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/IdeInfoFromProtobuf.java b/base/src/com/google/idea/blaze/base/sync/aspects/IdeInfoFromProtobuf.java
index 812148b..f3f0557 100644
--- a/base/src/com/google/idea/blaze/base/sync/aspects/IdeInfoFromProtobuf.java
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/IdeInfoFromProtobuf.java
@@ -16,25 +16,30 @@
 
 package com.google.idea.blaze.base.sync.aspects;
 
+import static java.util.stream.Collectors.toList;
+
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.ideinfo.AndroidRuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.CRuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.CIdeInfo;
 import com.google.idea.blaze.base.ideinfo.CToolchainIdeInfo;
-import com.google.idea.blaze.base.ideinfo.JavaRuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.Dependency;
+import com.google.idea.blaze.base.ideinfo.Dependency.DependencyType;
+import com.google.idea.blaze.base.ideinfo.JavaIdeInfo;
 import com.google.idea.blaze.base.ideinfo.JavaToolchainIdeInfo;
 import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
 import com.google.idea.blaze.base.ideinfo.ProtoLibraryLegacyInfo;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.PyIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.ideinfo.TestIdeInfo;
 import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
 import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
-import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.AndroidStudioIdeInfo;
-import com.google.repackaged.protobuf.ProtocolStringList;
+import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo;
 import java.util.Collection;
 import java.util.List;
 import javax.annotation.Nullable;
@@ -43,9 +48,8 @@
 public class IdeInfoFromProtobuf {
 
   @Nullable
-  public static RuleIdeInfo makeRuleIdeInfo(
-      WorkspaceLanguageSettings workspaceLanguageSettings,
-      AndroidStudioIdeInfo.RuleIdeInfo message) {
+  public static TargetIdeInfo makeTargetIdeInfo(
+      WorkspaceLanguageSettings workspaceLanguageSettings, IntellijIdeInfo.TargetIdeInfo message) {
     Kind kind = getKind(message);
     if (kind == null) {
       return null;
@@ -54,33 +58,57 @@
       return null;
     }
 
-    Label label = new Label(message.getLabel());
+    final TargetKey key;
+    if (message.hasKey()) {
+      key = makeTargetKey(message.getKey());
+    } else {
+      key = TargetKey.forPlainTarget(new Label(message.getLabel()));
+    }
+
     ArtifactLocation buildFile = getBuildFile(message);
 
-    Collection<Label> dependencies = makeLabelListFromProtobuf(message.getDependenciesList());
-    Collection<Label> runtimeDeps = makeLabelListFromProtobuf(message.getRuntimeDepsList());
+    final Collection<Dependency> dependencies;
+    if (message.getDepsCount() > 0) {
+      dependencies =
+          message.getDepsList().stream().map(IdeInfoFromProtobuf::makeDependency).collect(toList());
+    } else {
+      dependencies =
+          Lists.newArrayListWithCapacity(
+              message.getDependenciesCount() + message.getRuntimeDepsCount());
+      dependencies.addAll(
+          makeDependencyListFromLabelList(
+              message.getDependenciesList(), DependencyType.COMPILE_TIME));
+      dependencies.addAll(
+          makeDependencyListFromLabelList(message.getRuntimeDepsList(), DependencyType.RUNTIME));
+    }
+
     Collection<String> tags = ImmutableList.copyOf(message.getTagsList());
 
     Collection<ArtifactLocation> sources = Lists.newArrayList();
-    CRuleIdeInfo cRuleIdeInfo = null;
-    if (message.hasCRuleIdeInfo()) {
-      cRuleIdeInfo = makeCRuleIdeInfo(message.getCRuleIdeInfo());
-      sources.addAll(cRuleIdeInfo.sources);
+    CIdeInfo cIdeInfo = null;
+    if (message.hasCIdeInfo()) {
+      cIdeInfo = makeCIdeInfo(message.getCIdeInfo());
+      sources.addAll(cIdeInfo.sources);
     }
     CToolchainIdeInfo cToolchainIdeInfo = null;
     if (message.hasCToolchainIdeInfo()) {
       cToolchainIdeInfo = makeCToolchainIdeInfo(message.getCToolchainIdeInfo());
     }
-    JavaRuleIdeInfo javaRuleIdeInfo = null;
-    if (message.hasJavaRuleIdeInfo()) {
-      javaRuleIdeInfo = makeJavaRuleIdeInfo(message.getJavaRuleIdeInfo());
+    JavaIdeInfo javaIdeInfo = null;
+    if (message.hasJavaIdeInfo()) {
+      javaIdeInfo = makeJavaIdeInfo(message.getJavaIdeInfo());
       Collection<ArtifactLocation> javaSources =
-          makeArtifactLocationList(message.getJavaRuleIdeInfo().getSourcesList());
+          makeArtifactLocationList(message.getJavaIdeInfo().getSourcesList());
       sources.addAll(javaSources);
     }
-    AndroidRuleIdeInfo androidRuleIdeInfo = null;
-    if (message.hasAndroidRuleIdeInfo()) {
-      androidRuleIdeInfo = makeAndroidRuleIdeInfo(message.getAndroidRuleIdeInfo());
+    AndroidIdeInfo androidIdeInfo = null;
+    if (message.hasAndroidIdeInfo()) {
+      androidIdeInfo = makeAndroidIdeInfo(message.getAndroidIdeInfo());
+    }
+    PyIdeInfo pyIdeInfo = null;
+    if (message.hasPyIdeInfo()) {
+      pyIdeInfo = makePyIdeInfo(message.getPyIdeInfo());
+      sources.addAll(pyIdeInfo.sources);
     }
     TestIdeInfo testIdeInfo = null;
     if (message.hasTestInfo()) {
@@ -96,46 +124,76 @@
       javaToolchainIdeInfo = makeJavaToolchainIdeInfo(message.getJavaToolchainIdeInfo());
     }
 
-    return new RuleIdeInfo(
-        label,
+    return new TargetIdeInfo(
+        key,
         kind,
         buildFile,
         dependencies,
-        runtimeDeps,
         tags,
         sources,
-        cRuleIdeInfo,
+        cIdeInfo,
         cToolchainIdeInfo,
-        javaRuleIdeInfo,
-        androidRuleIdeInfo,
+        javaIdeInfo,
+        androidIdeInfo,
+        pyIdeInfo,
         testIdeInfo,
         protoLibraryLegacyInfo,
         javaToolchainIdeInfo);
   }
 
+  private static Collection<Dependency> makeDependencyListFromLabelList(
+      List<String> dependencyList, Dependency.DependencyType dependencyType) {
+    return dependencyList
+        .stream()
+        .map(dep -> new Dependency(TargetKey.forPlainTarget(new Label(dep)), dependencyType))
+        .collect(toList());
+  }
+
+  private static TargetKey makeTargetKey(IntellijIdeInfo.TargetKey key) {
+    return TargetKey.forGeneralTarget(
+        new Label(key.getLabel()), Strings.emptyToNull(key.getAspectId()));
+  }
+
+  private static Dependency makeDependency(IntellijIdeInfo.Dependency dep) {
+    return new Dependency(
+        makeTargetKey(dep.getTarget()), makeDependencyType(dep.getDependencyType()));
+  }
+
+  private static Dependency.DependencyType makeDependencyType(
+      IntellijIdeInfo.Dependency.DependencyType dependencyType) {
+    switch (dependencyType) {
+      case COMPILE_TIME:
+        return DependencyType.COMPILE_TIME;
+      case RUNTIME:
+        return DependencyType.RUNTIME;
+      default:
+        return DependencyType.COMPILE_TIME;
+    }
+  }
+
   @Nullable
-  private static ArtifactLocation getBuildFile(AndroidStudioIdeInfo.RuleIdeInfo message) {
+  private static ArtifactLocation getBuildFile(IntellijIdeInfo.TargetIdeInfo message) {
     if (message.hasBuildFileArtifactLocation()) {
       return makeArtifactLocation(message.getBuildFileArtifactLocation());
     }
     return null;
   }
 
-  private static CRuleIdeInfo makeCRuleIdeInfo(AndroidStudioIdeInfo.CRuleIdeInfo cRuleIdeInfo) {
-    List<ArtifactLocation> sources = makeArtifactLocationList(cRuleIdeInfo.getSourceList());
+  private static CIdeInfo makeCIdeInfo(IntellijIdeInfo.CIdeInfo cIdeInfo) {
+    List<ArtifactLocation> sources = makeArtifactLocationList(cIdeInfo.getSourceList());
     List<ExecutionRootPath> transitiveIncludeDirectories =
-        makeExecutionRootPathList(cRuleIdeInfo.getTransitiveIncludeDirectoryList());
+        makeExecutionRootPathList(cIdeInfo.getTransitiveIncludeDirectoryList());
     List<ExecutionRootPath> transitiveQuoteIncludeDirectories =
-        makeExecutionRootPathList(cRuleIdeInfo.getTransitiveQuoteIncludeDirectoryList());
+        makeExecutionRootPathList(cIdeInfo.getTransitiveQuoteIncludeDirectoryList());
     List<ExecutionRootPath> transitiveSystemIncludeDirectories =
-        makeExecutionRootPathList(cRuleIdeInfo.getTransitiveSystemIncludeDirectoryList());
+        makeExecutionRootPathList(cIdeInfo.getTransitiveSystemIncludeDirectoryList());
 
-    CRuleIdeInfo.Builder builder =
-        CRuleIdeInfo.builder()
+    CIdeInfo.Builder builder =
+        CIdeInfo.builder()
             .addSources(sources)
             .addTransitiveIncludeDirectories(transitiveIncludeDirectories)
             .addTransitiveQuoteIncludeDirectories(transitiveQuoteIncludeDirectories)
-            .addTransitiveDefines(cRuleIdeInfo.getTransitiveDefineList())
+            .addTransitiveDefines(cIdeInfo.getTransitiveDefineList())
             .addTransitiveSystemIncludeDirectories(transitiveSystemIncludeDirectories);
 
     return builder.build();
@@ -150,7 +208,7 @@
   }
 
   private static CToolchainIdeInfo makeCToolchainIdeInfo(
-      AndroidStudioIdeInfo.CToolchainIdeInfo cToolchainIdeInfo) {
+      IntellijIdeInfo.CToolchainIdeInfo cToolchainIdeInfo) {
     Collection<ExecutionRootPath> builtInIncludeDirectories =
         makeExecutionRootPathList(cToolchainIdeInfo.getBuiltInIncludeDirectoryList());
     ExecutionRootPath cppExecutable = new ExecutionRootPath(cToolchainIdeInfo.getCppExecutable());
@@ -177,40 +235,40 @@
     return builder.build();
   }
 
-  private static JavaRuleIdeInfo makeJavaRuleIdeInfo(
-      AndroidStudioIdeInfo.JavaRuleIdeInfo javaRuleIdeInfo) {
-    return new JavaRuleIdeInfo(
-        makeLibraryArtifactList(javaRuleIdeInfo.getJarsList()),
-        makeLibraryArtifactList(javaRuleIdeInfo.getGeneratedJarsList()),
-        javaRuleIdeInfo.hasFilteredGenJar()
-            ? makeLibraryArtifact(javaRuleIdeInfo.getFilteredGenJar())
+  private static JavaIdeInfo makeJavaIdeInfo(IntellijIdeInfo.JavaIdeInfo javaIdeInfo) {
+    return new JavaIdeInfo(
+        makeLibraryArtifactList(javaIdeInfo.getJarsList()),
+        makeLibraryArtifactList(javaIdeInfo.getGeneratedJarsList()),
+        javaIdeInfo.hasFilteredGenJar()
+            ? makeLibraryArtifact(javaIdeInfo.getFilteredGenJar())
             : null,
-        javaRuleIdeInfo.hasPackageManifest()
-            ? makeArtifactLocation(javaRuleIdeInfo.getPackageManifest())
+        javaIdeInfo.hasPackageManifest()
+            ? makeArtifactLocation(javaIdeInfo.getPackageManifest())
             : null,
-        javaRuleIdeInfo.hasJdeps() ? makeArtifactLocation(javaRuleIdeInfo.getJdeps()) : null);
+        javaIdeInfo.hasJdeps() ? makeArtifactLocation(javaIdeInfo.getJdeps()) : null);
   }
 
-  private static AndroidRuleIdeInfo makeAndroidRuleIdeInfo(
-      AndroidStudioIdeInfo.AndroidRuleIdeInfo androidRuleIdeInfo) {
-    return new AndroidRuleIdeInfo(
-        makeArtifactLocationList(androidRuleIdeInfo.getResourcesList()),
-        androidRuleIdeInfo.getJavaPackage(),
-        androidRuleIdeInfo.getGenerateResourceClass(),
-        androidRuleIdeInfo.hasManifest()
-            ? makeArtifactLocation(androidRuleIdeInfo.getManifest())
+  private static AndroidIdeInfo makeAndroidIdeInfo(IntellijIdeInfo.AndroidIdeInfo androidIdeInfo) {
+    return new AndroidIdeInfo(
+        makeArtifactLocationList(androidIdeInfo.getResourcesList()),
+        androidIdeInfo.getJavaPackage(),
+        androidIdeInfo.getGenerateResourceClass(),
+        androidIdeInfo.hasManifest() ? makeArtifactLocation(androidIdeInfo.getManifest()) : null,
+        androidIdeInfo.hasIdlJar() ? makeLibraryArtifact(androidIdeInfo.getIdlJar()) : null,
+        androidIdeInfo.hasResourceJar()
+            ? makeLibraryArtifact(androidIdeInfo.getResourceJar())
             : null,
-        androidRuleIdeInfo.hasIdlJar() ? makeLibraryArtifact(androidRuleIdeInfo.getIdlJar()) : null,
-        androidRuleIdeInfo.hasResourceJar()
-            ? makeLibraryArtifact(androidRuleIdeInfo.getResourceJar())
-            : null,
-        androidRuleIdeInfo.getHasIdlSources(),
-        !Strings.isNullOrEmpty(androidRuleIdeInfo.getLegacyResources())
-            ? new Label(androidRuleIdeInfo.getLegacyResources())
+        androidIdeInfo.getHasIdlSources(),
+        !Strings.isNullOrEmpty(androidIdeInfo.getLegacyResources())
+            ? new Label(androidIdeInfo.getLegacyResources())
             : null);
   }
 
-  private static TestIdeInfo makeTestIdeInfo(AndroidStudioIdeInfo.TestInfo testInfo) {
+  private static PyIdeInfo makePyIdeInfo(IntellijIdeInfo.PyIdeInfo info) {
+    return PyIdeInfo.builder().addSources(makeArtifactLocationList(info.getSourcesList())).build();
+  }
+
+  private static TestIdeInfo makeTestIdeInfo(IntellijIdeInfo.TestInfo testInfo) {
     String size = testInfo.getSize();
     TestIdeInfo.TestSize testSize = TestIdeInfo.DEFAULT_RULE_TEST_SIZE;
     if (!Strings.isNullOrEmpty(size)) {
@@ -235,7 +293,7 @@
   }
 
   private static ProtoLibraryLegacyInfo makeProtoLibraryLegacyInfo(
-      AndroidStudioIdeInfo.ProtoLibraryLegacyJavaIdeInfo protoLibraryLegacyJavaIdeInfo) {
+      IntellijIdeInfo.ProtoLibraryLegacyJavaIdeInfo protoLibraryLegacyJavaIdeInfo) {
     final ProtoLibraryLegacyInfo.ApiFlavor apiFlavor;
     if (protoLibraryLegacyJavaIdeInfo.getApiVersion() == 1) {
       apiFlavor = ProtoLibraryLegacyInfo.ApiFlavor.VERSION_1;
@@ -263,15 +321,15 @@
   }
 
   private static JavaToolchainIdeInfo makeJavaToolchainIdeInfo(
-      AndroidStudioIdeInfo.JavaToolchainIdeInfo javaToolchainIdeInfo) {
+      IntellijIdeInfo.JavaToolchainIdeInfo javaToolchainIdeInfo) {
     return new JavaToolchainIdeInfo(
         javaToolchainIdeInfo.getSourceVersion(), javaToolchainIdeInfo.getTargetVersion());
   }
 
   private static Collection<LibraryArtifact> makeLibraryArtifactList(
-      List<AndroidStudioIdeInfo.LibraryArtifact> jarsList) {
+      List<IntellijIdeInfo.LibraryArtifact> jarsList) {
     ImmutableList.Builder<LibraryArtifact> builder = ImmutableList.builder();
-    for (AndroidStudioIdeInfo.LibraryArtifact libraryArtifact : jarsList) {
+    for (IntellijIdeInfo.LibraryArtifact libraryArtifact : jarsList) {
       LibraryArtifact lib = makeLibraryArtifact(libraryArtifact);
       if (lib != null) {
         builder.add(lib);
@@ -282,7 +340,7 @@
 
   @Nullable
   private static LibraryArtifact makeLibraryArtifact(
-      AndroidStudioIdeInfo.LibraryArtifact libraryArtifact) {
+      IntellijIdeInfo.LibraryArtifact libraryArtifact) {
     ArtifactLocation classJar =
         libraryArtifact.hasJar() ? makeArtifactLocation(libraryArtifact.getJar()) : null;
     ArtifactLocation iJar =
@@ -302,9 +360,9 @@
   }
 
   private static List<ArtifactLocation> makeArtifactLocationList(
-      List<AndroidStudioIdeInfo.ArtifactLocation> sourcesList) {
+      List<IntellijIdeInfo.ArtifactLocation> sourcesList) {
     ImmutableList.Builder<ArtifactLocation> builder = ImmutableList.builder();
-    for (AndroidStudioIdeInfo.ArtifactLocation pbArtifactLocation : sourcesList) {
+    for (IntellijIdeInfo.ArtifactLocation pbArtifactLocation : sourcesList) {
       ArtifactLocation loc = makeArtifactLocation(pbArtifactLocation);
       if (loc != null) {
         builder.add(loc);
@@ -315,7 +373,7 @@
 
   @Nullable
   private static ArtifactLocation makeArtifactLocation(
-      AndroidStudioIdeInfo.ArtifactLocation pbArtifactLocation) {
+      IntellijIdeInfo.ArtifactLocation pbArtifactLocation) {
     if (pbArtifactLocation == null) {
       return null;
     }
@@ -323,20 +381,13 @@
         .setRootExecutionPathFragment(pbArtifactLocation.getRootExecutionPathFragment())
         .setRelativePath(pbArtifactLocation.getRelativePath())
         .setIsSource(pbArtifactLocation.getIsSource())
+        .setIsExternal(pbArtifactLocation.getIsExternal())
         .build();
   }
 
-  private static Collection<Label> makeLabelListFromProtobuf(ProtocolStringList dependenciesList) {
-    ImmutableList.Builder<Label> dependenciesBuilder = ImmutableList.builder();
-    for (String dependencyLabel : dependenciesList) {
-      dependenciesBuilder.add(new Label(dependencyLabel));
-    }
-    return dependenciesBuilder.build();
-  }
-
   @Nullable
-  private static Kind getKind(AndroidStudioIdeInfo.RuleIdeInfo rule) {
-    String kindString = rule.getKindString();
+  private static Kind getKind(IntellijIdeInfo.TargetIdeInfo target) {
+    String kindString = target.getKindString();
     if (!Strings.isNullOrEmpty(kindString)) {
       return Kind.fromString(kindString);
     }
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategy.java b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategy.java
new file mode 100644
index 0000000..598d8b6
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategy.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.sync.aspects.strategy;
+
+import com.google.idea.blaze.base.command.BlazeCommand;
+import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Indirection for our various ways of calling the aspect. */
+public interface AspectStrategy {
+  String getName();
+
+  void modifyIdeInfoCommand(BlazeCommand.Builder blazeCommandBuilder);
+
+  void modifyIdeResolveCommand(BlazeCommand.Builder blazeCommandBuilder);
+
+  String getAspectOutputFileExtension();
+
+  IntellijIdeInfo.TargetIdeInfo readAspectFile(InputStream inputStream) throws IOException;
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyNative.java b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyNative.java
new file mode 100644
index 0000000..df88c4f
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyNative.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.sync.aspects.strategy;
+
+import com.google.idea.blaze.base.command.BlazeCommand;
+import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Aspect strategy for native. */
+public class AspectStrategyNative implements AspectStrategy {
+  @Override
+  public String getName() {
+    return "NativeAspect";
+  }
+
+  @Override
+  public void modifyIdeInfoCommand(BlazeCommand.Builder blazeCommandBuilder) {
+    blazeCommandBuilder
+        .addBlazeFlags("--aspects=AndroidStudioInfoAspect")
+        .addBlazeFlags("--output_groups=ide-info");
+  }
+
+  @Override
+  public void modifyIdeResolveCommand(BlazeCommand.Builder blazeCommandBuilder) {
+    blazeCommandBuilder
+        .addBlazeFlags("--aspects=AndroidStudioInfoAspect")
+        .addBlazeFlags("--output_groups=ide-resolve");
+  }
+
+  @Override
+  public String getAspectOutputFileExtension() {
+    return ".aswb-build";
+  }
+
+  @Override
+  public IntellijIdeInfo.TargetIdeInfo readAspectFile(InputStream inputStream) throws IOException {
+    return IntellijIdeInfo.TargetIdeInfo.parseFrom(inputStream);
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProvider.java b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProvider.java
new file mode 100644
index 0000000..960a79c
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProvider.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.sync.aspects.strategy;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.project.Project;
+
+/** Extension point for providing an aspect strategy */
+public interface AspectStrategyProvider {
+  ExtensionPointName<AspectStrategyProvider> EP_NAME =
+      ExtensionPointName.create("com.google.idea.blaze.AspectStrategyProvider");
+
+  AspectStrategy getAspectStrategy(Project project);
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProviderBazel.java b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProviderBazel.java
new file mode 100644
index 0000000..9042523
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategyProviderBazel.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.sync.aspects.strategy;
+
+import com.google.idea.common.experiments.BoolExperiment;
+import com.intellij.openapi.project.Project;
+
+class AspectStrategyProviderBazel implements AspectStrategyProvider {
+  BoolExperiment useSkylarkAspect = new BoolExperiment("use.skylark.aspect.bazel", false);
+
+  @Override
+  public AspectStrategy getAspectStrategy(Project project) {
+    return useSkylarkAspect.getValue() ? new AspectStrategySkylark() : new AspectStrategyNative();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategySkylark.java b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategySkylark.java
new file mode 100644
index 0000000..0139f87
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/aspects/strategy/AspectStrategySkylark.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.sync.aspects.strategy;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.idea.blaze.base.command.BlazeCommand;
+import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo;
+import com.google.repackaged.protobuf.TextFormat;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+/** Aspect strategy for Skylark. */
+public class AspectStrategySkylark implements AspectStrategy {
+
+  @Override
+  public String getName() {
+    return "SkylarkAspect";
+  }
+
+  protected String getAspectFlag() {
+    return "--aspects=@bazel_tools://tools/ide/intellij_info.bzl%intellij_info_aspect";
+  }
+
+  @Override
+  public void modifyIdeInfoCommand(BlazeCommand.Builder blazeCommandBuilder) {
+    blazeCommandBuilder
+        .addBlazeFlags(getAspectFlag())
+        .addBlazeFlags("--output_groups=intellij-info-text");
+  }
+
+  @Override
+  public void modifyIdeResolveCommand(BlazeCommand.Builder blazeCommandBuilder) {
+    blazeCommandBuilder
+        .addBlazeFlags(getAspectFlag())
+        .addBlazeFlags("--output_groups=intellij-resolve");
+  }
+
+  @Override
+  public String getAspectOutputFileExtension() {
+    return ".intellij-info.txt";
+  }
+
+  @Override
+  public IntellijIdeInfo.TargetIdeInfo readAspectFile(InputStream inputStream) throws IOException {
+    IntellijIdeInfo.TargetIdeInfo.Builder builder = IntellijIdeInfo.TargetIdeInfo.newBuilder();
+    TextFormat.Parser parser = TextFormat.Parser.newBuilder().setAllowUnknownFields(true).build();
+    parser.merge(new InputStreamReader(inputStream, UTF_8), builder);
+    return builder.build();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/libraries/BlazeLibraryCollector.java b/base/src/com/google/idea/blaze/base/sync/libraries/BlazeLibraryCollector.java
new file mode 100644
index 0000000..9e029ed
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/libraries/BlazeLibraryCollector.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.sync.libraries;
+
+import com.google.common.collect.Lists;
+import com.google.idea.blaze.base.model.BlazeLibrary;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/** Collects libraries from the sync data using all contributors. */
+public class BlazeLibraryCollector {
+  public static List<BlazeLibrary> getLibraries(BlazeProjectData blazeProjectData) {
+    List<BlazeLibrary> result = Lists.newArrayList();
+    List<LibrarySource> librarySources = Lists.newArrayList();
+    for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
+      LibrarySource librarySource = syncPlugin.getLibrarySource(blazeProjectData);
+      if (librarySource != null) {
+        librarySources.add(librarySource);
+      }
+    }
+    for (LibrarySource librarySource : librarySources) {
+      result.addAll(librarySource.getLibraries());
+    }
+    Predicate<BlazeLibrary> libraryFilter =
+        librarySources
+            .stream()
+            .map(LibrarySource::getLibraryFilter)
+            .filter(Objects::nonNull)
+            .reduce(Predicate::and)
+            .orElse(o -> true);
+    return result.stream().filter(libraryFilter).collect(Collectors.toList());
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/projectstructure/LibraryEditor.java b/base/src/com/google/idea/blaze/base/sync/libraries/LibraryEditor.java
similarity index 80%
rename from java/src/com/google/idea/blaze/java/sync/projectstructure/LibraryEditor.java
rename to base/src/com/google/idea/blaze/base/sync/libraries/LibraryEditor.java
index aa6ba5e..8452d19 100644
--- a/java/src/com/google/idea/blaze/java/sync/projectstructure/LibraryEditor.java
+++ b/base/src/com/google/idea/blaze/base/sync/libraries/LibraryEditor.java
@@ -13,16 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.google.idea.blaze.java.sync.projectstructure;
+package com.google.idea.blaze.base.sync.libraries;
 
+import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import com.google.idea.blaze.base.model.BlazeLibrary;
 import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.LibraryKey;
 import com.google.idea.blaze.base.scope.BlazeContext;
 import com.google.idea.blaze.base.scope.output.PrintOutput;
+import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
-import com.google.idea.blaze.java.sync.BlazeJavaSyncAugmenter;
-import com.google.idea.blaze.java.sync.model.BlazeLibrary;
-import com.google.idea.blaze.java.sync.model.LibraryKey;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.roots.ModifiableRootModel;
@@ -31,7 +32,10 @@
 import com.intellij.openapi.roots.libraries.Library;
 import com.intellij.openapi.roots.libraries.LibraryTable;
 import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
 import java.util.Set;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 /** Edits IntelliJ libraries */
@@ -52,13 +56,6 @@
     }
     context.output(PrintOutput.log(String.format("Workspace has %d libraries", libraries.size())));
 
-    Set<String> externallyAddedLibraries = Sets.newHashSet();
-    for (BlazeJavaSyncAugmenter augmenter :
-        BlazeJavaSyncAugmenter.getActiveSyncAgumenters(
-            blazeProjectData.workspaceLanguageSettings)) {
-      externallyAddedLibraries.addAll(augmenter.getExternallyAddedLibraries(blazeProjectData));
-    }
-
     LibraryTable libraryTable = ProjectLibraryTable.getInstance(project);
     LibraryTable.ModifiableModel libraryTableModel = libraryTable.getModifiableModel();
     try {
@@ -72,15 +69,31 @@
       }
 
       // Garbage collect unused libraries
+      List<LibrarySource> librarySources = Lists.newArrayList();
+      for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
+        LibrarySource librarySource = syncPlugin.getLibrarySource(blazeProjectData);
+        if (librarySource != null) {
+          librarySources.add(librarySource);
+        }
+      }
+      Predicate<Library> gcRetentionFilter =
+          librarySources
+              .stream()
+              .map(LibrarySource::getGcRetentionFilter)
+              .filter(Objects::nonNull)
+              .reduce(Predicate::or)
+              .orElse(o -> false);
+
       Set<LibraryKey> newLibraryKeys =
           libraries.stream().map((blazeLibrary) -> blazeLibrary.key).collect(Collectors.toSet());
       for (LibraryKey libraryKey : intelliJLibraryState) {
         String libraryIntellijName = libraryKey.getIntelliJLibraryName();
-        if (!newLibraryKeys.contains(libraryKey)
-            && !externallyAddedLibraries.contains(libraryIntellijName)) {
+        if (!newLibraryKeys.contains(libraryKey)) {
           Library library = libraryTable.getLibraryByName(libraryIntellijName);
-          if (library != null) {
-            libraryTableModel.removeLibrary(library);
+          if (!gcRetentionFilter.test(library)) {
+            if (library != null) {
+              libraryTableModel.removeLibrary(library);
+            }
           }
         }
       }
diff --git a/base/src/com/google/idea/blaze/base/sync/libraries/LibrarySource.java b/base/src/com/google/idea/blaze/base/sync/libraries/LibrarySource.java
new file mode 100644
index 0000000..5d15a8d
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/sync/libraries/LibrarySource.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.sync.libraries;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.model.BlazeLibrary;
+import com.intellij.openapi.roots.libraries.Library;
+import java.util.Collection;
+import java.util.function.Predicate;
+import javax.annotation.Nullable;
+
+/** Interface for contributing libraries to sync. */
+public interface LibrarySource {
+
+  /** Called during the project structure phase to get libraries. */
+  Collection<? extends BlazeLibrary> getLibraries();
+
+  /**
+   * Returns a filter on libraries.
+   *
+   * <p>Return false from the predicate to filter the library. If any filter returns false the
+   * library is discarded.
+   */
+  @Nullable
+  Predicate<BlazeLibrary> getLibraryFilter();
+
+  /**
+   * Returns a filter that allows sources to retain libraries during library garbage collection.
+   *
+   * <p>Return true from the predicate to spare a library during garbage collection. If any filter
+   * returns true the library is spared.
+   */
+  @Nullable
+  Predicate<Library> getGcRetentionFilter();
+
+  /** Adapter class */
+  abstract class Adapter implements LibrarySource {
+    @Override
+    public Collection<? extends BlazeLibrary> getLibraries() {
+      return ImmutableList.of();
+    }
+
+    @Nullable
+    @Override
+    public Predicate<BlazeLibrary> getLibraryFilter() {
+      return null;
+    }
+
+    @Nullable
+    @Override
+    public Predicate<Library> getGcRetentionFilter() {
+      return null;
+    }
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/sync/projectview/LanguageSupport.java b/base/src/com/google/idea/blaze/base/sync/projectview/LanguageSupport.java
index f0e43ab..8e5b39f 100644
--- a/base/src/com/google/idea/blaze/base/sync/projectview/LanguageSupport.java
+++ b/base/src/com/google/idea/blaze/base/sync/projectview/LanguageSupport.java
@@ -25,6 +25,7 @@
 import com.google.idea.blaze.base.scope.output.IssueOutput;
 import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
 import com.intellij.openapi.diagnostic.Logger;
+import java.util.EnumSet;
 import java.util.Set;
 
 /** Reads the user's language preferences from the project view. */
@@ -35,13 +36,21 @@
   public static WorkspaceLanguageSettings createWorkspaceLanguageSettings(
       BlazeContext context, ProjectViewSet projectViewSet) {
     WorkspaceType workspaceType = projectViewSet.getScalarValue(WorkspaceTypeSection.KEY);
+    Set<WorkspaceType> supportedTypes = supportedWorkspaceTypes();
+    if (workspaceType != null && !supportedTypes.contains(workspaceType)) {
+      IssueOutput.error(
+              String.format(
+                  "Workspace type '%s' is not supported by this plugin", workspaceType.getName()))
+          .submit(context);
+      return null;
+    }
     if (workspaceType == null) {
+      // if no workspace type is specified, prioritize by enum ordinal.
       for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
-        WorkspaceType pluginWorkspaceType = syncPlugin.getDefaultWorkspaceType();
-        if (pluginWorkspaceType != null) {
-          if (workspaceType == null || workspaceType.ordinal() < pluginWorkspaceType.ordinal()) {
-            workspaceType = pluginWorkspaceType;
-          }
+        WorkspaceType recommendedType = syncPlugin.getDefaultWorkspaceType();
+        if (recommendedType != null
+            && (workspaceType == null || workspaceType.ordinal() > recommendedType.ordinal())) {
+          workspaceType = recommendedType;
         }
       }
     }
@@ -51,18 +60,23 @@
       return null;
     }
 
-    Set<LanguageClass> activeLanguages = Sets.newHashSet();
-    for (LanguageClass languageClass : workspaceType.getLanguages()) {
-      activeLanguages.add(languageClass);
-    }
+    Set<LanguageClass> activeLanguages = Sets.newHashSet(workspaceType.getLanguages());
     activeLanguages.addAll(projectViewSet.listItems(AdditionalLanguagesSection.KEY));
 
-    Set<LanguageClass> supportedLanguages = Sets.newHashSet();
-    for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
-      supportedLanguages.addAll(syncPlugin.getSupportedLanguagesInWorkspace(workspaceType));
+    Set<LanguageClass> supportedLanguages = supportedLanguagesForWorkspaceType(workspaceType);
+    Set<LanguageClass> availableLanguages = EnumSet.noneOf(LanguageClass.class);
+    for (WorkspaceType type : supportedTypes) {
+      availableLanguages.addAll(supportedLanguagesForWorkspaceType(type));
     }
 
     for (LanguageClass languageClass : activeLanguages) {
+      if (!availableLanguages.contains(languageClass)) {
+        IssueOutput.error(
+                String.format(
+                    "Language '%s' is not supported by this plugin", languageClass.getName()))
+            .submit(context);
+        return null;
+      }
       if (!supportedLanguages.contains(languageClass)) {
         IssueOutput.error(
                 String.format(
@@ -73,6 +87,25 @@
       }
     }
 
+    activeLanguages.add(LanguageClass.GENERIC);
     return new WorkspaceLanguageSettings(workspaceType, activeLanguages);
   }
+
+  /** The {@link WorkspaceType}s supported by this plugin */
+  private static Set<WorkspaceType> supportedWorkspaceTypes() {
+    Set<WorkspaceType> supportedTypes = EnumSet.noneOf(WorkspaceType.class);
+    for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
+      supportedTypes.addAll(syncPlugin.getSupportedWorkspaceTypes());
+    }
+    return supportedTypes;
+  }
+
+  /** @return The set of {@link LanguageClass}'s supported for this {@link WorkspaceType}s. */
+  private static Set<LanguageClass> supportedLanguagesForWorkspaceType(WorkspaceType type) {
+    Set<LanguageClass> supportedLanguages = EnumSet.noneOf(LanguageClass.class);
+    for (BlazeSyncPlugin syncPlugin : BlazeSyncPlugin.EP_NAME.getExtensions()) {
+      supportedLanguages.addAll(syncPlugin.getSupportedLanguagesInWorkspace(type));
+    }
+    return supportedLanguages;
+  }
 }
diff --git a/base/src/com/google/idea/blaze/base/sync/projectview/ProjectViewRuleImportFilter.java b/base/src/com/google/idea/blaze/base/sync/projectview/ProjectViewTargetImportFilter.java
similarity index 70%
rename from base/src/com/google/idea/blaze/base/sync/projectview/ProjectViewRuleImportFilter.java
rename to base/src/com/google/idea/blaze/base/sync/projectview/ProjectViewTargetImportFilter.java
index 5cd8b16..788f58d 100644
--- a/base/src/com/google/idea/blaze/base/sync/projectview/ProjectViewRuleImportFilter.java
+++ b/base/src/com/google/idea/blaze/base/sync/projectview/ProjectViewTargetImportFilter.java
@@ -16,8 +16,8 @@
 package com.google.idea.blaze.base.sync.projectview;
 
 import com.google.common.collect.Sets;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
 import com.google.idea.blaze.base.ideinfo.Tags;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
@@ -28,12 +28,12 @@
 import java.util.Set;
 
 /** Filters rules into source/library depending on the project view. */
-public class ProjectViewRuleImportFilter {
+public class ProjectViewTargetImportFilter {
   private final ImportRoots importRoots;
   private final Set<Label> importTargetOutputs;
   private final Set<Label> excludedTargets;
 
-  public ProjectViewRuleImportFilter(
+  public ProjectViewTargetImportFilter(
       Project project, WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet) {
     this.importRoots =
         ImportRoots.builder(workspaceRoot, Blaze.getBuildSystem(project))
@@ -44,19 +44,19 @@
     this.excludedTargets = Sets.newHashSet(projectViewSet.listItems(ExcludeTargetSection.KEY));
   }
 
-  public boolean isSourceRule(RuleIdeInfo rule) {
-    return importRoots.importAsSource(rule.label) && !importTargetOutput(rule);
+  public boolean isSourceTarget(TargetIdeInfo target) {
+    return importRoots.importAsSource(target.key.label) && !importTargetOutput(target);
   }
 
-  private boolean importTargetOutput(RuleIdeInfo rule) {
-    return rule.tags.contains(Tags.RULE_TAG_IMPORT_TARGET_OUTPUT)
-        || rule.tags.contains(Tags.RULE_TAG_IMPORT_AS_LIBRARY_LEGACY)
-        || importTargetOutputs.contains(rule.label);
+  private boolean importTargetOutput(TargetIdeInfo target) {
+    return target.tags.contains(Tags.TARGET_TAG_IMPORT_TARGET_OUTPUT)
+        || target.tags.contains(Tags.TARGET_TAG_IMPORT_AS_LIBRARY_LEGACY)
+        || importTargetOutputs.contains(target.key.label);
   }
 
-  public boolean excludeTarget(RuleIdeInfo rule) {
-    return excludedTargets.contains(rule.label)
-        || rule.tags.contains(Tags.RULE_TAG_PROVIDED_BY_SDK)
-        || rule.tags.contains(Tags.RULE_TAG_EXCLUDE_TARGET);
+  public boolean excludeTarget(TargetIdeInfo target) {
+    return excludedTargets.contains(target.key.label)
+        || target.tags.contains(Tags.TARGET_TAG_PROVIDED_BY_SDK)
+        || target.tags.contains(Tags.TARGET_TAG_EXCLUDE_TARGET);
   }
 }
diff --git a/base/src/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoder.java b/base/src/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoder.java
index ccdeb54..78ad46f 100644
--- a/base/src/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoder.java
+++ b/base/src/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoder.java
@@ -22,7 +22,7 @@
 import java.util.List;
 import java.util.stream.Collectors;
 
-/** Decodes android_studio_ide_info.proto ArtifactLocation file paths */
+/** Decodes intellij_ide_info.proto ArtifactLocation file paths */
 public interface ArtifactLocationDecoder extends Serializable {
 
   File decode(ArtifactLocation artifactLocation);
diff --git a/base/src/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoderImpl.java b/base/src/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoderImpl.java
index f31cace..92ff6b8 100644
--- a/base/src/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoderImpl.java
+++ b/base/src/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoderImpl.java
@@ -18,7 +18,7 @@
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
 import java.io.File;
 
-/** Decodes android_studio_ide_info.proto ArtifactLocation file paths */
+/** Decodes intellij_ide_info.proto ArtifactLocation file paths */
 public class ArtifactLocationDecoderImpl implements ArtifactLocationDecoder {
   private static final long serialVersionUID = 1L;
 
@@ -33,10 +33,12 @@
   @Override
   public File decode(ArtifactLocation artifactLocation) {
     if (artifactLocation.isSource) {
-      File root = pathResolver.findPackageRoot(artifactLocation.getRelativePath());
-      return new File(root, artifactLocation.getRelativePath());
-    } else {
-      return new File(blazeRoots.executionRoot, artifactLocation.getExecutionRootRelativePath());
+      if (artifactLocation.isExternal) {
+        return new File(blazeRoots.externalSourceRoot, artifactLocation.relativePath);
+      }
+      File root = pathResolver.findPackageRoot(artifactLocation.relativePath);
+      return new File(root, artifactLocation.relativePath);
     }
+    return new File(blazeRoots.executionRoot, artifactLocation.getExecutionRootRelativePath());
   }
 }
diff --git a/base/src/com/google/idea/blaze/base/sync/workspace/BlazeRoots.java b/base/src/com/google/idea/blaze/base/sync/workspace/BlazeRoots.java
index bd54a39..aa63765 100644
--- a/base/src/com/google/idea/blaze/base/sync/workspace/BlazeRoots.java
+++ b/base/src/com/google/idea/blaze/base/sync/workspace/BlazeRoots.java
@@ -16,56 +16,58 @@
 package com.google.idea.blaze.base.sync.workspace;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
 import com.google.idea.blaze.base.command.info.BlazeInfo;
 import com.google.idea.blaze.base.io.FileAttributeProvider;
 import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.scope.BlazeContext;
-import com.google.idea.blaze.base.settings.Blaze;
 import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
 import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
 import java.io.File;
 import java.io.Serializable;
 import java.util.List;
-import javax.annotation.Nullable;
 
 /** The data output by BlazeInfo. */
 public class BlazeRoots implements Serializable {
   public static final long serialVersionUID = 3L;
   private static final Logger LOG = Logger.getInstance(BlazeRoots.class);
 
-  public static ListenableFuture<BlazeRoots> compute(
-      Project project, WorkspaceRoot workspaceRoot, BlazeContext context) {
-    BuildSystem buildSystem = Blaze.getBuildSystem(project);
-    ListenableFuture<ImmutableMap<String, String>> blazeInfoDataFuture =
-        BlazeInfo.getInstance()
-            .runBlazeInfo(context, buildSystem, workspaceRoot, ImmutableList.of());
-    return Futures.transform(
-        blazeInfoDataFuture,
-        new Function<ImmutableMap<String, String>, BlazeRoots>() {
-          @Nullable
-          @Override
-          public BlazeRoots apply(@Nullable ImmutableMap<String, String> blazeInfoData) {
-            // This method is supposed to throw if the input is null
-            // but the input is not allowed to be null.
-            if (blazeInfoData == null) {
-              throw new NullPointerException("blazeInfoData is not allowed to be null");
-            }
-            return build(
-                workspaceRoot,
-                getOrThrow(buildSystem, blazeInfoData, BlazeInfo.EXECUTION_ROOT_KEY),
-                getOrThrow(buildSystem, blazeInfoData, BlazeInfo.PACKAGE_PATH_KEY),
-                getOrThrow(buildSystem, blazeInfoData, BlazeInfo.blazeBinKey(buildSystem)),
-                getOrThrow(buildSystem, blazeInfoData, BlazeInfo.blazeGenfilesKey(buildSystem)));
-          }
-        });
+  public static BlazeRoots build(
+      BuildSystem buildSystem,
+      WorkspaceRoot workspaceRoot,
+      ImmutableMap<String, String> blazeInfo) {
+    return build(
+        workspaceRoot,
+        getOrThrow(buildSystem, blazeInfo, BlazeInfo.EXECUTION_ROOT_KEY),
+        getOrThrow(buildSystem, blazeInfo, BlazeInfo.PACKAGE_PATH_KEY),
+        getOrThrow(buildSystem, blazeInfo, BlazeInfo.blazeBinKey(buildSystem)),
+        getOrThrow(buildSystem, blazeInfo, BlazeInfo.blazeGenfilesKey(buildSystem)),
+        getOrThrow(buildSystem, blazeInfo, BlazeInfo.OUTPUT_BASE_KEY));
+  }
+
+  private static BlazeRoots build(
+      WorkspaceRoot workspaceRoot,
+      String execRootString,
+      String packagePathString,
+      String blazeBinRoot,
+      String blazeGenfilesRoot,
+      String externalSourceRoot) {
+    List<File> packagePaths = parsePackagePaths(workspaceRoot.toString(), packagePathString.trim());
+    File executionRoot = new File(execRootString.trim());
+    ExecutionRootPath blazeBinExecutionRootPath =
+        ExecutionRootPath.createAncestorRelativePath(executionRoot, new File(blazeBinRoot));
+    ExecutionRootPath blazeGenfilesExecutionRootPath =
+        ExecutionRootPath.createAncestorRelativePath(executionRoot, new File(blazeGenfilesRoot));
+    File externalSourceRootFile = new File(externalSourceRoot.trim());
+    LOG.assertTrue(blazeBinExecutionRootPath != null);
+    LOG.assertTrue(blazeGenfilesExecutionRootPath != null);
+    return new BlazeRoots(
+        executionRoot,
+        packagePaths,
+        blazeBinExecutionRootPath,
+        blazeGenfilesExecutionRootPath,
+        externalSourceRootFile);
   }
 
   private static String getOrThrow(
@@ -78,24 +80,6 @@
     return value;
   }
 
-  private static BlazeRoots build(
-      WorkspaceRoot workspaceRoot,
-      String execRootString,
-      String packagePathString,
-      String blazeBinRoot,
-      String blazeGenfilesRoot) {
-    List<File> packagePaths = parsePackagePaths(workspaceRoot.toString(), packagePathString.trim());
-    File executionRoot = new File(execRootString.trim());
-    ExecutionRootPath blazeBinExecutionRootPath =
-        ExecutionRootPath.createAncestorRelativePath(executionRoot, new File(blazeBinRoot));
-    ExecutionRootPath blazeGenfilesExecutionRootPath =
-        ExecutionRootPath.createAncestorRelativePath(executionRoot, new File(blazeGenfilesRoot));
-    LOG.assertTrue(blazeBinExecutionRootPath != null);
-    LOG.assertTrue(blazeGenfilesExecutionRootPath != null);
-    return new BlazeRoots(
-        executionRoot, packagePaths, blazeBinExecutionRootPath, blazeGenfilesExecutionRootPath);
-  }
-
   private static List<File> parsePackagePaths(String workspaceRoot, String packagePathString) {
     String[] paths = packagePathString.split(":");
     List<File> packagePaths = Lists.newArrayListWithCapacity(paths.length);
@@ -113,20 +97,32 @@
   public final List<File> packagePaths;
   public final ExecutionRootPath blazeBinExecutionRootPath;
   public final ExecutionRootPath blazeGenfilesExecutionRootPath;
+  public final File externalSourceRoot;
 
   @VisibleForTesting
   public BlazeRoots(
       File executionRoot,
       List<File> packagePaths,
       ExecutionRootPath blazeBinExecutionRootPath,
-      ExecutionRootPath blazeGenfilesExecutionRootPath) {
+      ExecutionRootPath blazeGenfilesExecutionRootPath,
+      File externalSourceRoot) {
     this.executionRoot = executionRoot;
     this.packagePaths = packagePaths;
     this.blazeBinExecutionRootPath = blazeBinExecutionRootPath;
     this.blazeGenfilesExecutionRootPath = blazeGenfilesExecutionRootPath;
+    this.externalSourceRoot = externalSourceRoot;
   }
 
   public File getGenfilesDirectory() {
     return blazeGenfilesExecutionRootPath.getFileRootedAt(executionRoot);
   }
+
+  public File getBlazeBinDirectory() {
+    return blazeBinExecutionRootPath.getFileRootedAt(executionRoot);
+  }
+
+  public boolean isOutputArtifact(ExecutionRootPath path) {
+    return ExecutionRootPath.isAncestor(blazeGenfilesExecutionRootPath, path, false)
+        || ExecutionRootPath.isAncestor(blazeBinExecutionRootPath, path, false);
+  }
 }
diff --git a/base/src/com/google/idea/blaze/base/targetmaps/ReverseDependencyMap.java b/base/src/com/google/idea/blaze/base/targetmaps/ReverseDependencyMap.java
new file mode 100644
index 0000000..fc300df
--- /dev/null
+++ b/base/src/com/google/idea/blaze/base/targetmaps/ReverseDependencyMap.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.targetmaps;
+
+import com.google.common.collect.ImmutableMultimap;
+import com.google.idea.blaze.base.ideinfo.Dependency;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
+
+/** Handy class to create an reverse dep map of all targets */
+public class ReverseDependencyMap {
+  public static ImmutableMultimap<TargetKey, TargetKey> createRdepsMap(TargetMap targetMap) {
+    ImmutableMultimap.Builder<TargetKey, TargetKey> builder = ImmutableMultimap.builder();
+    for (TargetIdeInfo target : targetMap.targets()) {
+      TargetKey key = target.key;
+      for (Dependency dep : target.dependencies) {
+        TargetKey depKey = dep.targetKey;
+        if (targetMap.contains(depKey)) {
+          builder.put(depKey, key);
+        }
+      }
+    }
+    return builder.build();
+  }
+}
diff --git a/base/src/com/google/idea/blaze/base/rulemaps/SourceToRuleMap.java b/base/src/com/google/idea/blaze/base/targetmaps/SourceToTargetMap.java
similarity index 76%
rename from base/src/com/google/idea/blaze/base/rulemaps/SourceToRuleMap.java
rename to base/src/com/google/idea/blaze/base/targetmaps/SourceToTargetMap.java
index df650ea..46b458f 100644
--- a/base/src/com/google/idea/blaze/base/rulemaps/SourceToRuleMap.java
+++ b/base/src/com/google/idea/blaze/base/targetmaps/SourceToTargetMap.java
@@ -13,25 +13,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.google.idea.blaze.base.rulemaps;
+package com.google.idea.blaze.base.targetmaps;
 
 import com.google.common.collect.ImmutableCollection;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.project.Project;
 import java.io.File;
 
 /** Maps source files to their respective targets */
-public interface SourceToRuleMap {
+public interface SourceToTargetMap {
 
-  static SourceToRuleMap getInstance(Project project) {
-    return ServiceManager.getService(project, SourceToRuleMap.class);
+  static SourceToTargetMap getInstance(Project project) {
+    return ServiceManager.getService(project, SourceToTargetMap.class);
   }
 
   /** Returns a set of targets that will cause the file to build */
   ImmutableCollection<Label> getTargetsToBuildForSourceFile(File file);
 
   /** Returns the rules that contain a given source file */
-  ImmutableCollection<RuleKey> getRulesForSourceFile(File file);
+  ImmutableCollection<TargetKey> getRulesForSourceFile(File file);
 }
diff --git a/base/src/com/google/idea/blaze/base/rulemaps/SourceToRuleMapImpl.java b/base/src/com/google/idea/blaze/base/targetmaps/SourceToTargetMapImpl.java
similarity index 74%
rename from base/src/com/google/idea/blaze/base/rulemaps/SourceToRuleMapImpl.java
rename to base/src/com/google/idea/blaze/base/targetmaps/SourceToTargetMapImpl.java
index af86d33..af965aa 100644
--- a/base/src/com/google/idea/blaze/base/rulemaps/SourceToRuleMapImpl.java
+++ b/base/src/com/google/idea/blaze/base/targetmaps/SourceToTargetMapImpl.java
@@ -13,14 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.google.idea.blaze.base.rulemaps;
+package com.google.idea.blaze.base.targetmaps;
 
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMultimap;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
@@ -37,15 +37,15 @@
 import javax.annotation.Nullable;
 
 /** Maps source files to their respective targets */
-public class SourceToRuleMapImpl implements SourceToRuleMap {
+public class SourceToTargetMapImpl implements SourceToTargetMap {
   private final Project project;
-  private ImmutableMultimap<File, RuleKey> sourceToTargetMap;
+  private ImmutableMultimap<File, TargetKey> sourceToTargetMap;
 
-  public static SourceToRuleMapImpl getImpl(Project project) {
-    return (SourceToRuleMapImpl) ServiceManager.getService(project, SourceToRuleMap.class);
+  public static SourceToTargetMapImpl getImpl(Project project) {
+    return (SourceToTargetMapImpl) ServiceManager.getService(project, SourceToTargetMap.class);
   }
 
-  public SourceToRuleMapImpl(Project project) {
+  public SourceToTargetMapImpl(Project project) {
     this.project = project;
   }
 
@@ -59,18 +59,18 @@
     return ImmutableList.copyOf(
         getRulesForSourceFile(sourceFile)
             .stream()
-            .map(blazeProjectData.ruleMap::get)
+            .map(blazeProjectData.targetMap::get)
             .filter(Objects::nonNull)
             // TODO(tomlu): For non-plain targets we need to rdep our way back to a target to build
             // Without this, you won't be able to invoke "build" on (say) a proto_library
-            .filter(RuleIdeInfo::isPlainTarget)
-            .map(rule -> rule.label)
+            .filter(TargetIdeInfo::isPlainTarget)
+            .map(rule -> rule.key.label)
             .collect(Collectors.toList()));
   }
 
   @Override
-  public ImmutableCollection<RuleKey> getRulesForSourceFile(File sourceFile) {
-    ImmutableMultimap<File, RuleKey> sourceToTargetMap = getSourceToTargetMap();
+  public ImmutableCollection<TargetKey> getRulesForSourceFile(File sourceFile) {
+    ImmutableMultimap<File, TargetKey> sourceToTargetMap = getSourceToTargetMap();
     if (sourceToTargetMap == null) {
       return ImmutableList.of();
     }
@@ -78,7 +78,7 @@
   }
 
   @Nullable
-  private synchronized ImmutableMultimap<File, RuleKey> getSourceToTargetMap() {
+  private synchronized ImmutableMultimap<File, TargetKey> getSourceToTargetMap() {
     if (this.sourceToTargetMap == null) {
       this.sourceToTargetMap = initSourceToTargetMap();
     }
@@ -90,17 +90,17 @@
   }
 
   @Nullable
-  private ImmutableMultimap<File, RuleKey> initSourceToTargetMap() {
+  private ImmutableMultimap<File, TargetKey> initSourceToTargetMap() {
     BlazeProjectData blazeProjectData =
         BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
     if (blazeProjectData == null) {
       return null;
     }
     ArtifactLocationDecoder artifactLocationDecoder = blazeProjectData.artifactLocationDecoder;
-    ImmutableMultimap.Builder<File, RuleKey> sourceToTargetMap = ImmutableMultimap.builder();
-    for (RuleIdeInfo rule : blazeProjectData.ruleMap.rules()) {
-      RuleKey key = rule.key;
-      for (ArtifactLocation sourceArtifact : rule.sources) {
+    ImmutableMultimap.Builder<File, TargetKey> sourceToTargetMap = ImmutableMultimap.builder();
+    for (TargetIdeInfo target : blazeProjectData.targetMap.targets()) {
+      TargetKey key = target.key;
+      for (ArtifactLocation sourceArtifact : target.sources) {
         sourceToTargetMap.put(artifactLocationDecoder.decode(sourceArtifact), key);
       }
     }
diff --git a/base/src/com/google/idea/blaze/base/treeview/WorkspaceRootNode.java b/base/src/com/google/idea/blaze/base/treeview/WorkspaceRootNode.java
index d0c504a..e414394 100644
--- a/base/src/com/google/idea/blaze/base/treeview/WorkspaceRootNode.java
+++ b/base/src/com/google/idea/blaze/base/treeview/WorkspaceRootNode.java
@@ -23,7 +23,6 @@
 import com.google.idea.blaze.base.settings.Blaze;
 import com.google.idea.blaze.base.settings.BlazeUserSettings;
 import com.google.idea.blaze.base.sync.projectview.ImportRoots;
-import com.google.idea.common.experiments.BoolExperiment;
 import com.intellij.ide.projectView.PresentationData;
 import com.intellij.ide.projectView.ViewSettings;
 import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode;
@@ -46,10 +45,6 @@
  * workspace directory.
  */
 public class WorkspaceRootNode extends PsiDirectoryNode {
-
-  private static final BoolExperiment COLLAPSE_PROJECT_VIEW =
-      new BoolExperiment("collapse.project.view", true);
-
   private final WorkspaceRoot workspaceRoot;
 
   public WorkspaceRootNode(
@@ -60,9 +55,6 @@
 
   @Override
   public Collection<AbstractTreeNode> getChildrenImpl() {
-    if (!COLLAPSE_PROJECT_VIEW.getValue()) {
-      return super.getChildrenImpl();
-    }
     if (!BlazeUserSettings.getInstance().getCollapseProjectView()) {
       return super.getChildrenImpl();
     }
@@ -76,7 +68,6 @@
       return super.getChildrenImpl();
     }
 
-    WorkspaceRoot workspaceRoot = WorkspaceRoot.fromProject(project);
     ImportRoots importRoots =
         ImportRoots.builder(workspaceRoot, Blaze.getBuildSystem(project))
             .add(projectViewSet)
diff --git a/base/src/com/google/idea/blaze/base/wizard2/BlazeSelectWorkspaceOption.java b/base/src/com/google/idea/blaze/base/wizard2/BlazeSelectWorkspaceOption.java
index 1c8654d..11bf115 100644
--- a/base/src/com/google/idea/blaze/base/wizard2/BlazeSelectWorkspaceOption.java
+++ b/base/src/com/google/idea/blaze/base/wizard2/BlazeSelectWorkspaceOption.java
@@ -17,14 +17,19 @@
 
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
+import java.io.File;
 
 /** Provides an option on the "Select workspace" screen */
 public interface BlazeSelectWorkspaceOption extends BlazeWizardOption {
   /** @return The workspace root that will be created after commit. */
   WorkspaceRoot getWorkspaceRoot();
 
-  /** @return a location to use when browsing for workspace paths. */
-  WorkspaceRoot getTemporaryWorkspaceRoot();
+  /** @return A workspace path resolver to use during wizard validation. */
+  WorkspacePathResolver getWorkspacePathResolver();
+
+  /** @return A root directory to use for browsing workspace paths. */
+  File getFileBrowserRoot();
 
   /** @return the name of the workspace. Used to generate default project names. */
   String getWorkspaceName();
diff --git a/base/src/com/google/idea/blaze/base/wizard2/GenerateFromBuildFileSelectProjectViewOption.java b/base/src/com/google/idea/blaze/base/wizard2/GenerateFromBuildFileSelectProjectViewOption.java
index dce0fb7..b65059b 100644
--- a/base/src/com/google/idea/blaze/base/wizard2/GenerateFromBuildFileSelectProjectViewOption.java
+++ b/base/src/com/google/idea/blaze/base/wizard2/GenerateFromBuildFileSelectProjectViewOption.java
@@ -15,10 +15,10 @@
  */
 package com.google.idea.blaze.base.wizard2;
 
+import com.google.common.base.Strings;
 import com.google.idea.blaze.base.bazel.BuildSystemProvider;
 import com.google.idea.blaze.base.model.primitives.TargetExpression;
 import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.projectview.ProjectView;
 import com.google.idea.blaze.base.projectview.parser.ProjectViewParser;
 import com.google.idea.blaze.base.projectview.section.ListSection;
@@ -27,6 +27,7 @@
 import com.google.idea.blaze.base.projectview.section.sections.TargetSection;
 import com.google.idea.blaze.base.projectview.section.sections.TextBlock;
 import com.google.idea.blaze.base.projectview.section.sections.TextBlockSection;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
 import com.google.idea.blaze.base.ui.BlazeValidationResult;
 import com.google.idea.blaze.base.ui.UiUtil;
 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
@@ -91,8 +92,9 @@
     if (getBuildFilePath().isEmpty()) {
       return BlazeValidationResult.failure("BUILD file field cannot be empty.");
     }
-    WorkspaceRoot temporaryWorkspaceRoot = builder.getWorkspaceOption().getTemporaryWorkspaceRoot();
-    File file = temporaryWorkspaceRoot.fileForPath(new WorkspacePath(getBuildFilePath()));
+    WorkspacePathResolver workspacePathResolver =
+        builder.getWorkspaceOption().getWorkspacePathResolver();
+    File file = workspacePathResolver.resolveToFile(new WorkspacePath(getBuildFilePath()));
     if (!file.exists()) {
       return BlazeValidationResult.failure("BUILD file does not exist.");
     }
@@ -109,12 +111,11 @@
   @Nullable
   @Override
   public String getInitialProjectViewText() {
-    WorkspaceRoot temporaryWorkspaceRoot = builder.getWorkspaceOption().getTemporaryWorkspaceRoot();
-    WorkspacePath workspacePath = new WorkspacePath(getBuildFilePath());
-    return guessProjectViewFromLocation(
-        temporaryWorkspaceRoot,
-        temporaryWorkspaceRoot.workspacePathFor(
-            temporaryWorkspaceRoot.fileForPath(workspacePath).getParentFile()));
+    WorkspacePathResolver workspacePathResolver =
+        builder.getWorkspaceOption().getWorkspacePathResolver();
+    WorkspacePath workspacePath =
+        new WorkspacePath(Strings.nullToEmpty(new File(getBuildFilePath()).getParent()));
+    return guessProjectViewFromLocation(workspacePathResolver, workspacePath);
   }
 
   @Override
@@ -124,11 +125,11 @@
   }
 
   private static String guessProjectViewFromLocation(
-      WorkspaceRoot workspaceRoot, WorkspacePath workspacePath) {
+      WorkspacePathResolver workspacePathResolver, WorkspacePath workspacePath) {
 
     WorkspacePath mainModuleWorkspaceRelativePath = workspacePath;
     WorkspacePath testModuleWorkspaceRelativePath =
-        guessTestRelativePath(workspaceRoot, mainModuleWorkspaceRelativePath);
+        guessTestRelativePath(workspacePathResolver, mainModuleWorkspaceRelativePath);
 
     ListSection.Builder<DirectoryEntry> directorySectionBuilder =
         ListSection.builder(DirectorySection.KEY);
@@ -156,7 +157,7 @@
 
   @Nullable
   private static WorkspacePath guessTestRelativePath(
-      WorkspaceRoot workspaceRoot, WorkspacePath projectWorkspacePath) {
+      WorkspacePathResolver workspacePathResolver, WorkspacePath projectWorkspacePath) {
     String projectRelativePath = projectWorkspacePath.relativePath();
     String testBuildFileRelativePath = null;
     if (projectRelativePath.startsWith("java/")) {
@@ -165,7 +166,8 @@
       testBuildFileRelativePath = projectRelativePath.replaceFirst("/java/", "/javatests/");
     }
     if (testBuildFileRelativePath != null) {
-      File testBuildFile = workspaceRoot.fileForPath(new WorkspacePath(testBuildFileRelativePath));
+      File testBuildFile =
+          workspacePathResolver.resolveToFile(new WorkspacePath(testBuildFileRelativePath));
       if (testBuildFile.exists()) {
         return new WorkspacePath(testBuildFileRelativePath);
       }
@@ -190,17 +192,19 @@
     FileChooserDialog chooser =
         FileChooserFactory.getInstance().createFileChooser(descriptor, null, null);
 
-    WorkspaceRoot temporaryWorkspaceRoot = builder.getWorkspaceOption().getTemporaryWorkspaceRoot();
+    WorkspacePathResolver workspacePathResolver =
+        builder.getWorkspaceOption().getWorkspacePathResolver();
 
-    File startingLocation = temporaryWorkspaceRoot.directory();
+    File fileBrowserRoot = builder.getWorkspaceOption().getFileBrowserRoot();
+    File startingLocation = fileBrowserRoot;
     String buildFilePath = getBuildFilePath();
     if (!buildFilePath.isEmpty() && WorkspacePath.validate(buildFilePath)) {
       // If the user has typed part of the path then clicked the '...', try to start from the
       // partial state
       buildFilePath = StringUtil.trimEnd(buildFilePath, '/');
       if (WorkspacePath.validate(buildFilePath)) {
-        File fileLocation = temporaryWorkspaceRoot.fileForPath(new WorkspacePath(buildFilePath));
-        if (fileLocation.exists()) {
+        File fileLocation = workspacePathResolver.resolveToFile(new WorkspacePath(buildFilePath));
+        if (fileLocation.exists() && FileUtil.isAncestor(fileBrowserRoot, fileLocation, true)) {
           startingLocation = fileLocation;
         }
       }
@@ -212,8 +216,7 @@
       return;
     }
     VirtualFile file = files[0];
-    String newWorkspacePath =
-        FileUtil.getRelativePath(temporaryWorkspaceRoot.directory(), new File(file.getPath()));
+    String newWorkspacePath = FileUtil.getRelativePath(fileBrowserRoot, new File(file.getPath()));
     buildFilePathField.setText(newWorkspacePath);
   }
 }
diff --git a/base/src/com/google/idea/blaze/base/wizard2/ImportFromWorkspaceProjectViewOption.java b/base/src/com/google/idea/blaze/base/wizard2/ImportFromWorkspaceProjectViewOption.java
index b7f2340..e4803be 100644
--- a/base/src/com/google/idea/blaze/base/wizard2/ImportFromWorkspaceProjectViewOption.java
+++ b/base/src/com/google/idea/blaze/base/wizard2/ImportFromWorkspaceProjectViewOption.java
@@ -17,8 +17,8 @@
 
 import com.google.common.collect.Lists;
 import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.projectview.ProjectViewStorageManager;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
 import com.google.idea.blaze.base.ui.BlazeValidationError;
 import com.google.idea.blaze.base.ui.BlazeValidationResult;
 import com.google.idea.blaze.base.ui.UiUtil;
@@ -91,8 +91,9 @@
     if (!WorkspacePath.validate(getProjectViewPath(), errors)) {
       return BlazeValidationResult.failure(errors.get(0));
     }
-    WorkspaceRoot temporaryWorkspaceRoot = builder.getWorkspaceOption().getTemporaryWorkspaceRoot();
-    File file = temporaryWorkspaceRoot.fileForPath(getSharedProjectView());
+    WorkspacePathResolver workspacePathResolver =
+        builder.getWorkspaceOption().getWorkspacePathResolver();
+    File file = workspacePathResolver.resolveToFile(getSharedProjectView());
     if (!file.exists()) {
       return BlazeValidationResult.failure("Project view file does not exist.");
     }
@@ -135,17 +136,18 @@
     FileChooserDialog chooser =
         FileChooserFactory.getInstance().createFileChooser(descriptor, null, null);
 
-    WorkspaceRoot temporaryWorkspaceRoot = builder.getWorkspaceOption().getTemporaryWorkspaceRoot();
-
-    File startingLocation = temporaryWorkspaceRoot.directory();
+    WorkspacePathResolver workspacePathResolver =
+        builder.getWorkspaceOption().getWorkspacePathResolver();
+    File fileBrowserRoot = builder.getWorkspaceOption().getFileBrowserRoot();
+    File startingLocation = fileBrowserRoot;
     String projectViewPath = getProjectViewPath();
     if (!projectViewPath.isEmpty()) {
       // If the user has typed part of the path then clicked the '...', try to start from the
       // partial state
       projectViewPath = StringUtil.trimEnd(projectViewPath, '/');
       if (WorkspacePath.validate(projectViewPath)) {
-        File fileLocation = temporaryWorkspaceRoot.fileForPath(new WorkspacePath(projectViewPath));
-        if (fileLocation.exists()) {
+        File fileLocation = workspacePathResolver.resolveToFile(new WorkspacePath(projectViewPath));
+        if (fileLocation.exists() && FileUtil.isAncestor(fileBrowserRoot, fileLocation, true)) {
           startingLocation = fileLocation;
         }
       }
@@ -158,18 +160,17 @@
     }
     VirtualFile file = files[0];
 
-    if (!FileUtil.isAncestor(temporaryWorkspaceRoot.directory().getPath(), file.getPath(), true)) {
+    if (!FileUtil.isAncestor(fileBrowserRoot.getPath(), file.getPath(), true)) {
       Messages.showErrorDialog(
           String.format(
               "You must choose a project view file under %s. "
                   + "To use an external project view, please use the 'Copy external' option.",
-              temporaryWorkspaceRoot.directory().getPath()),
+              fileBrowserRoot.getPath()),
           "Cannot Use Project View File");
       return;
     }
 
-    String newWorkspacePath =
-        FileUtil.getRelativePath(temporaryWorkspaceRoot.directory(), new File(file.getPath()));
+    String newWorkspacePath = FileUtil.getRelativePath(fileBrowserRoot, new File(file.getPath()));
     projectViewPathField.setText(newWorkspacePath);
   }
 }
diff --git a/base/src/com/google/idea/blaze/base/wizard2/UseExistingBazelWorkspaceOption.java b/base/src/com/google/idea/blaze/base/wizard2/UseExistingBazelWorkspaceOption.java
index c810f64..0b96d06 100644
--- a/base/src/com/google/idea/blaze/base/wizard2/UseExistingBazelWorkspaceOption.java
+++ b/base/src/com/google/idea/blaze/base/wizard2/UseExistingBazelWorkspaceOption.java
@@ -17,6 +17,8 @@
 
 import com.google.idea.blaze.base.bazel.BuildSystemProvider;
 import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl;
 import com.google.idea.blaze.base.ui.BlazeValidationResult;
 import icons.BlazeIcons;
 import java.io.File;
@@ -29,6 +31,11 @@
   }
 
   @Override
+  public WorkspacePathResolver getWorkspacePathResolver() {
+    return new WorkspacePathResolverImpl(getWorkspaceRoot());
+  }
+
+  @Override
   protected boolean isWorkspaceRoot(File file) {
     return BuildSystemProvider.getWorkspaceRootProvider(BuildSystem.Bazel).isWorkspaceRoot(file);
   }
diff --git a/base/src/com/google/idea/blaze/base/wizard2/UseExistingWorkspaceOption.java b/base/src/com/google/idea/blaze/base/wizard2/UseExistingWorkspaceOption.java
index cff86ed..ed4e227 100644
--- a/base/src/com/google/idea/blaze/base/wizard2/UseExistingWorkspaceOption.java
+++ b/base/src/com/google/idea/blaze/base/wizard2/UseExistingWorkspaceOption.java
@@ -33,7 +33,6 @@
 import java.io.File;
 import java.util.Arrays;
 import java.util.stream.Collectors;
-import javax.annotation.Nullable;
 import javax.swing.Icon;
 import javax.swing.JButton;
 import javax.swing.JComponent;
@@ -102,10 +101,9 @@
     return new WorkspaceRoot(new File(getDirectory()));
   }
 
-  @Nullable
   @Override
-  public WorkspaceRoot getTemporaryWorkspaceRoot() {
-    return getWorkspaceRoot();
+  public File getFileBrowserRoot() {
+    return new File(getDirectory());
   }
 
   @Override
diff --git a/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeEditProjectViewControl.java b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeEditProjectViewControl.java
index 81ace4e..594d652 100644
--- a/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeEditProjectViewControl.java
+++ b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeEditProjectViewControl.java
@@ -36,6 +36,7 @@
 import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
 import com.google.idea.blaze.base.sync.projectview.LanguageSupport;
 import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
 import com.google.idea.blaze.base.ui.BlazeValidationError;
 import com.google.idea.blaze.base.ui.BlazeValidationResult;
 import com.google.idea.blaze.base.ui.UiUtil;
@@ -83,7 +84,6 @@
   private JTextField projectNameField;
   private HashCode paramsHash;
   private WorkspaceRoot workspaceRoot;
-  private WorkspaceRoot temporaryWorkspaceRoot;
 
   public BlazeEditProjectViewControl(BlazeNewProjectBuilder builder, Disposable parentDisposable) {
     this.projectViewUi = new ProjectViewUi(parentDisposable);
@@ -141,7 +141,6 @@
     BlazeSelectProjectViewOption projectViewOption = builder.getProjectViewOption();
     String workspaceName = workspaceOption.getWorkspaceName();
     WorkspaceRoot workspaceRoot = workspaceOption.getWorkspaceRoot();
-    WorkspaceRoot temporaryWorkspaceRoot = workspaceOption.getTemporaryWorkspaceRoot();
     WorkspacePath workspacePath = projectViewOption.getSharedProjectView();
     String initialProjectViewText = projectViewOption.getInitialProjectViewText();
 
@@ -150,7 +149,6 @@
             .newHasher()
             .putUnencodedChars(workspaceName)
             .putUnencodedChars(workspaceRoot.toString())
-            .putUnencodedChars(temporaryWorkspaceRoot.toString())
             .putUnencodedChars(workspacePath != null ? workspacePath.toString() : "")
             .putUnencodedChars(initialProjectViewText != null ? initialProjectViewText : "")
             .hash();
@@ -161,7 +159,7 @@
       init(
           workspaceName,
           workspaceRoot,
-          temporaryWorkspaceRoot,
+          workspaceOption.getWorkspacePathResolver(),
           workspacePath,
           initialProjectViewText);
     }
@@ -170,11 +168,10 @@
   private void init(
       String workspaceName,
       WorkspaceRoot workspaceRoot,
-      WorkspaceRoot temporaryWorkspaceRoot,
+      WorkspacePathResolver workspacePathResolver,
       @Nullable WorkspacePath sharedProjectView,
       @Nullable String initialProjectViewText) {
     this.workspaceRoot = workspaceRoot;
-    this.temporaryWorkspaceRoot = temporaryWorkspaceRoot;
     projectNameField.setText(workspaceName);
     String defaultDataDir = getDefaultProjectDataDirectory(workspaceName);
     projectDataDirField.setText(defaultDataDir);
@@ -183,7 +180,7 @@
     File sharedProjectViewFile = null;
 
     if (sharedProjectView != null) {
-      sharedProjectViewFile = temporaryWorkspaceRoot.fileForPath(sharedProjectView);
+      sharedProjectViewFile = workspacePathResolver.resolveToFile(sharedProjectView);
 
       try {
         projectViewText =
@@ -201,11 +198,11 @@
     }
 
     projectViewUi.init(
-        temporaryWorkspaceRoot,
+        workspacePathResolver,
         projectViewText,
-        sharedProjectViewFile != null ? projectViewText : null,
-        sharedProjectViewFile,
-        sharedProjectViewFile != null,
+        sharedProjectView != null ? projectViewText : null,
+        sharedProjectView,
+        sharedProjectView != null,
         false /* allowEditShared - not allowed during import */);
   }
 
@@ -294,8 +291,7 @@
       return BlazeValidationResult.failure(projectViewParseError);
     }
 
-    ProjectViewValidator projectViewValidator =
-        new ProjectViewValidator(temporaryWorkspaceRoot, projectViewSet);
+    ProjectViewValidator projectViewValidator = new ProjectViewValidator(projectViewSet);
     ProgressManager.getInstance()
         .runProcessWithProgressSynchronously(
             projectViewValidator, "Validating Project", false, null);
@@ -314,14 +310,12 @@
   }
 
   private static class ProjectViewValidator implements Runnable {
-    private final WorkspaceRoot workspaceRoot;
     private final ProjectViewSet projectViewSet;
 
     private boolean success;
     List<IssueOutput> errors = Lists.newArrayList();
 
-    ProjectViewValidator(WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet) {
-      this.workspaceRoot = workspaceRoot;
+    ProjectViewValidator(ProjectViewSet projectViewSet) {
       this.projectViewSet = projectViewSet;
     }
 
@@ -348,8 +342,8 @@
       if (workspaceLanguageSettings == null) {
         return false;
       }
-      return ProjectViewVerifier.verifyProjectView(
-          context, workspaceRoot, projectViewSet, workspaceLanguageSettings);
+      return ProjectViewVerifier.verifyProjectViewNoDisk(
+          context, projectViewSet, workspaceLanguageSettings);
     }
   }
 
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/ArgumentCompletionContributorTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/ArgumentCompletionContributorTest.java
index 8db9661..aeff26f 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/ArgumentCompletionContributorTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/ArgumentCompletionContributorTest.java
@@ -19,6 +19,7 @@
 
 import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.codeInsight.lookup.LookupElement;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.testFramework.fixtures.CompletionAutoPopupTester;
@@ -50,10 +51,13 @@
         () -> {
           BuildFile file =
               createBuildFile(
-                  "BUILD", "def function(name, deps, srcs):", "  # empty function", "function(d");
+                  new WorkspacePath("BUILD"),
+                  "def function(name, deps, srcs):",
+                  "  # empty function",
+                  "function(d");
 
-          Editor editor = openFileInEditor(file.getVirtualFile());
-          setCaretPosition(editor, 2, "function(n".length());
+          Editor editor = editorTest.openFileInEditor(file.getVirtualFile());
+          editorTest.setCaretPosition(editor, 2, "function(n".length());
 
           LookupElement[] completionItems = testFixture.completeBasic();
           assertThat(completionItems).isNull();
@@ -69,15 +73,15 @@
         () -> {
           BuildFile file =
               createBuildFile(
-                  "BUILD",
+                  new WorkspacePath("BUILD"),
                   "def function(name, deps, srcs):",
                   "  # empty function",
                   "function(name = \"lib\")");
 
-          Editor editor = openFileInEditor(file.getVirtualFile());
-          setCaretPosition(editor, 2, "function(".length());
+          Editor editor = editorTest.openFileInEditor(file.getVirtualFile());
+          editorTest.setCaretPosition(editor, 2, "function(".length());
 
-          String[] completionItems = getCompletionItemsAsStrings();
+          String[] completionItems = editorTest.getCompletionItemsAsStrings();
           assertThat(completionItems).hasLength(4);
           assertThat(completionItems).asList().containsAllOf("name", "deps", "srcs", "function");
         });
@@ -89,10 +93,13 @@
         () -> {
           BuildFile file =
               createBuildFile(
-                  "BUILD", "def function(name, deps, srcs):", "  # empty function", "function(#");
+                  new WorkspacePath("BUILD"),
+                  "def function(name, deps, srcs):",
+                  "  # empty function",
+                  "function(#");
 
-          Editor editor = openFileInEditor(file.getVirtualFile());
-          setCaretPosition(editor, 2, "function(#".length());
+          Editor editor = editorTest.openFileInEditor(file.getVirtualFile());
+          editorTest.setCaretPosition(editor, 2, "function(#".length());
 
           completionTester.typeWithPauses("n");
           assertThat(testFixture.getLookup()).isNull();
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionAttributeCompletionContributorTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionAttributeCompletionContributorTest.java
index dba6e54..a6c6b9e 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionAttributeCompletionContributorTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionAttributeCompletionContributorTest.java
@@ -24,6 +24,7 @@
 import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpecProvider;
 import com.google.idea.blaze.base.lang.buildfile.language.semantics.RuleDefinition;
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.google.repackaged.devtools.build.lib.query2.proto.proto2api.Build;
 import com.intellij.codeInsight.lookup.LookupElement;
 import com.intellij.openapi.editor.Editor;
@@ -51,12 +52,12 @@
   public void testSimpleCompletion() {
     setRuleAndAttributes("sh_binary", "name", "deps", "srcs", "data");
 
-    BuildFile file = createBuildFile("BUILD", "sh_binary(");
+    BuildFile file = createBuildFile(new WorkspacePath("BUILD"), "sh_binary(");
 
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 0, "sh_binary(".length());
+    Editor editor = editorTest.openFileInEditor(file.getVirtualFile());
+    editorTest.setCaretPosition(editor, 0, "sh_binary(".length());
 
-    String[] completionItems = getCompletionItemsAsStrings();
+    String[] completionItems = editorTest.getCompletionItemsAsStrings();
     assertThat(completionItems).asList().containsAllOf("name", "deps", "srcs", "data");
   }
 
@@ -64,12 +65,12 @@
   public void testSimpleSingleCompletion() {
     setRuleAndAttributes("sh_binary", "name", "deps", "srcs", "data");
 
-    BuildFile file = createBuildFile("BUILD", "sh_binary(", "    n");
+    BuildFile file = createBuildFile(new WorkspacePath("BUILD"), "sh_binary(", "    n");
 
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 1, "    n".length());
+    Editor editor = editorTest.openFileInEditor(file.getVirtualFile());
+    editorTest.setCaretPosition(editor, 1, "    n".length());
 
-    String[] completionItems = getCompletionItemsAsStrings();
+    String[] completionItems = editorTest.getCompletionItemsAsStrings();
     assertThat(completionItems).isNull();
     assertFileContents(file, "sh_binary(", "    name");
   }
@@ -78,10 +79,10 @@
   public void testNoCompletionInUnknownRule() {
     setRuleAndAttributes("sh_binary", "name", "deps", "srcs", "data");
 
-    BuildFile file = createBuildFile("BUILD", "java_binary(");
+    BuildFile file = createBuildFile(new WorkspacePath("BUILD"), "java_binary(");
 
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 0, "java_binary(".length());
+    Editor editor = editorTest.openFileInEditor(file.getVirtualFile());
+    editorTest.setCaretPosition(editor, 0, "java_binary(".length());
 
     LookupElement[] completionItems = testFixture.completeBasic();
     assertThat(completionItems).isEmpty();
@@ -91,23 +92,23 @@
   public void testNoCompletionInComment() {
     setRuleAndAttributes("sh_binary", "name", "deps", "srcs", "data");
 
-    BuildFile file = createBuildFile("BUILD", "sh_binary(#");
+    BuildFile file = createBuildFile(new WorkspacePath("BUILD"), "sh_binary(#");
 
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 0, "sh_binary(#".length());
-    assertThat(getCompletionItemsAsStrings()).isEmpty();
+    Editor editor = editorTest.openFileInEditor(file.getVirtualFile());
+    editorTest.setCaretPosition(editor, 0, "sh_binary(#".length());
+    assertThat(editorTest.getCompletionItemsAsStrings()).isEmpty();
   }
 
   @Test
   public void testCompletionInSkylarkExtension() {
     setRuleAndAttributes("sh_binary", "name", "deps", "srcs", "data");
 
-    BuildFile file = createBuildFile("skylark.bzl", "native.sh_binary(");
+    BuildFile file = createBuildFile(new WorkspacePath("skylark.bzl"), "native.sh_binary(");
 
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 0, "native.sh_binary(".length());
+    Editor editor = editorTest.openFileInEditor(file.getVirtualFile());
+    editorTest.setCaretPosition(editor, 0, "native.sh_binary(".length());
 
-    String[] completionItems = getCompletionItemsAsStrings();
+    String[] completionItems = editorTest.getCompletionItemsAsStrings();
     assertThat(completionItems).asList().containsAllOf("name", "deps", "srcs", "data");
   }
 
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionCompletionContributorTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionCompletionContributorTest.java
index eb36aa4..6402fc2 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionCompletionContributorTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/BuiltInFunctionCompletionContributorTest.java
@@ -23,6 +23,7 @@
 import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpecProvider;
 import com.google.idea.blaze.base.lang.buildfile.language.semantics.RuleDefinition;
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.codeInsight.lookup.LookupElement;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.project.Project;
@@ -48,10 +49,10 @@
   public void testSimpleTopLevelCompletion() {
     setRules("java_library", "android_binary");
 
-    BuildFile file = createBuildFile("BUILD", "");
+    BuildFile file = createBuildFile(new WorkspacePath("BUILD"), "");
 
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 0, 0);
+    Editor editor = editorTest.openFileInEditor(file.getVirtualFile());
+    editorTest.setCaretPosition(editor, 0, 0);
 
     LookupElement[] completionItems = testFixture.completeBasic();
     assertThat(completionItems).hasLength(2);
@@ -65,32 +66,33 @@
   public void testUniqueTopLevelCompletion() {
     setRules("java_library", "android_binary");
 
-    BuildFile file = createBuildFile("BUILD", "ja");
+    BuildFile file = createBuildFile(new WorkspacePath("BUILD"), "ja");
 
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 0, 2);
+    Editor editor = editorTest.openFileInEditor(file.getVirtualFile());
+    editorTest.setCaretPosition(editor, 0, 2);
 
     LookupElement[] completionItems = testFixture.completeBasic();
     assertThat(completionItems).isNull();
 
     assertFileContents(file, "java_library()");
-    assertCaretPosition(editor, 0, "java_library(".length());
+    editorTest.assertCaretPosition(editor, 0, "java_library(".length());
   }
 
   @Test
   public void testSkylarkNativeCompletion() {
     setRules("java_library", "android_binary");
 
-    BuildFile file = createBuildFile("build_defs.bzl", "def function():", "  native.j");
+    BuildFile file =
+        createBuildFile(new WorkspacePath("build_defs.bzl"), "def function():", "  native.j");
 
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 1, "  native.j".length());
+    Editor editor = editorTest.openFileInEditor(file.getVirtualFile());
+    editorTest.setCaretPosition(editor, 1, "  native.j".length());
 
     LookupElement[] completionItems = testFixture.completeBasic();
     assertThat(completionItems).isNull();
 
     assertFileContents(file, "def function():", "  native.java_library()");
-    assertCaretPosition(editor, 1, "  native.java_library(".length());
+    editorTest.assertCaretPosition(editor, 1, "  native.java_library(".length());
   }
 
   @Test
@@ -99,10 +101,10 @@
 
     String[] contents = {"java_library(", "    name = \"lib\"", ""};
 
-    BuildFile file = createBuildFile("BUILD", contents);
+    BuildFile file = createBuildFile(new WorkspacePath("BUILD"), contents);
 
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 2, 0);
+    Editor editor = editorTest.openFileInEditor(file.getVirtualFile());
+    editorTest.setCaretPosition(editor, 2, 0);
 
     LookupElement[] completionItems = testFixture.completeBasic();
     assertThat(completionItems).isEmpty();
@@ -113,12 +115,12 @@
   public void testNoCompletionInComment() {
     setRules("java_library", "android_binary");
 
-    BuildFile file = createBuildFile("BUILD", "#java");
+    BuildFile file = createBuildFile(new WorkspacePath("BUILD"), "#java");
 
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 0, "#java".length());
+    Editor editor = editorTest.openFileInEditor(file.getVirtualFile());
+    editorTest.setCaretPosition(editor, 0, "#java".length());
 
-    assertThat(getCompletionItemsAsStrings()).isEmpty();
+    assertThat(editorTest.getCompletionItemsAsStrings()).isEmpty();
   }
 
   private void setRules(String... ruleNames) {
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/FilePathCompletionTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/FilePathCompletionTest.java
index a6024e5..a13d261 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/FilePathCompletionTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/FilePathCompletionTest.java
@@ -19,6 +19,7 @@
 
 import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.vfs.VirtualFile;
 import org.junit.Test;
@@ -31,25 +32,25 @@
 
   @Test
   public void testUniqueDirectoryCompleted() {
-    BuildFile file = createBuildFile("java/BUILD", "'//'");
+    BuildFile file = createBuildFile(new WorkspacePath("java/BUILD"), "'//'");
 
-    Editor editor = openFileInEditor(file);
-    setCaretPosition(editor, 0, "'//".length());
+    Editor editor = editorTest.openFileInEditor(file);
+    editorTest.setCaretPosition(editor, 0, "'//".length());
 
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "'//java'");
     // check caret remains inside closing quote
-    assertCaretPosition(editor, 0, "'//java".length());
+    editorTest.assertCaretPosition(editor, 0, "'//java".length());
   }
 
   @Test
   public void testUniqueMultiSegmentDirectoryCompleted() {
-    BuildFile file = createBuildFile("java/com/google/BUILD", "'//'");
+    BuildFile file = createBuildFile(new WorkspacePath("java/com/google/BUILD"), "'//'");
 
-    Editor editor = openFileInEditor(file);
-    setCaretPosition(editor, 0, "'//".length());
+    Editor editor = editorTest.openFileInEditor(file);
+    editorTest.setCaretPosition(editor, 0, "'//".length());
 
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "'//java/com/google'");
   }
 
@@ -58,66 +59,66 @@
   // next segment and complete again
   @Test
   public void testMultiStageCompletion() {
-    createDirectory("foo");
-    createDirectory("bar");
-    createDirectory("other");
-    createDirectory("other/foo");
-    createDirectory("other/bar");
+    workspace.createDirectory(new WorkspacePath("foo"));
+    workspace.createDirectory(new WorkspacePath("bar"));
+    workspace.createDirectory(new WorkspacePath("other"));
+    workspace.createDirectory(new WorkspacePath("other/foo"));
+    workspace.createDirectory(new WorkspacePath("other/bar"));
 
-    BuildFile file = createBuildFile("BUILD", "'//'");
+    BuildFile file = createBuildFile(new WorkspacePath("BUILD"), "'//'");
 
-    Editor editor = openFileInEditor(file);
-    setCaretPosition(editor, 0, "'//".length());
+    Editor editor = editorTest.openFileInEditor(file);
+    editorTest.setCaretPosition(editor, 0, "'//".length());
 
-    String[] completionItems = getCompletionItemsAsStrings();
+    String[] completionItems = editorTest.getCompletionItemsAsStrings();
     assertThat(completionItems).hasLength(3);
 
-    performTypingAction(editor, 'o');
-    assertThat(completeIfUnique()).isTrue();
+    editorTest.performTypingAction(editor, 'o');
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "'//other'");
-    assertCaretPosition(editor, 0, "'//other".length());
+    editorTest.assertCaretPosition(editor, 0, "'//other".length());
 
-    performTypingAction(editor, '/');
-    performTypingAction(editor, 'f');
-    assertThat(completeIfUnique()).isTrue();
+    editorTest.performTypingAction(editor, '/');
+    editorTest.performTypingAction(editor, 'f');
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "'//other/foo'");
-    assertCaretPosition(editor, 0, "'//other/foo".length());
+    editorTest.assertCaretPosition(editor, 0, "'//other/foo".length());
   }
 
   @Test
   public void testCompletionSuggestionString() {
-    createDirectory("foo");
-    createDirectory("bar");
-    createDirectory("other");
-    createDirectory("ostrich/foo");
-    createDirectory("ostrich/fooz");
+    workspace.createDirectory(new WorkspacePath("foo"));
+    workspace.createDirectory(new WorkspacePath("bar"));
+    workspace.createDirectory(new WorkspacePath("other"));
+    workspace.createDirectory(new WorkspacePath("ostrich/foo"));
+    workspace.createDirectory(new WorkspacePath("ostrich/fooz"));
 
-    VirtualFile file = createAndSetCaret("BUILD", "'//o<caret>'");
+    VirtualFile file = createAndSetCaret(new WorkspacePath("BUILD"), "'//o<caret>'");
 
-    String[] completionItems = getCompletionItemsAsSuggestionStrings();
+    String[] completionItems = editorTest.getCompletionItemsAsSuggestionStrings();
     assertThat(completionItems).asList().containsExactly("other", "ostrich");
 
-    performTypingAction(testFixture.getEditor(), 's');
+    editorTest.performTypingAction(testFixture.getEditor(), 's');
 
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "'//ostrich'");
 
-    completionItems = getCompletionItemsAsSuggestionStrings();
+    completionItems = editorTest.getCompletionItemsAsSuggestionStrings();
     assertThat(completionItems).asList().containsExactly("/foo", "/fooz");
 
-    performTypingAction(testFixture.getEditor(), '/');
+    editorTest.performTypingAction(testFixture.getEditor(), '/');
 
-    completionItems = getCompletionItemsAsSuggestionStrings();
+    completionItems = editorTest.getCompletionItemsAsSuggestionStrings();
     assertThat(completionItems).asList().containsExactly("foo", "fooz");
 
-    performTypingAction(testFixture.getEditor(), 'f');
+    editorTest.performTypingAction(testFixture.getEditor(), 'f');
 
-    completionItems = getCompletionItemsAsSuggestionStrings();
+    completionItems = editorTest.getCompletionItemsAsSuggestionStrings();
     assertThat(completionItems).asList().containsExactly("foo", "fooz");
   }
 
-  private VirtualFile createAndSetCaret(String filePath, String... fileContents) {
-    VirtualFile file = createFile(filePath, fileContents);
+  private VirtualFile createAndSetCaret(WorkspacePath workspacePath, String... fileContents) {
+    VirtualFile file = workspace.createFile(workspacePath, fileContents);
     testFixture.configureFromExistingVirtualFile(file);
     return file;
   }
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/LocalSymbolCompletionTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/LocalSymbolCompletionTest.java
index 1381c31..d29676a 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/LocalSymbolCompletionTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/LocalSymbolCompletionTest.java
@@ -41,7 +41,7 @@
   public void testLocalVariable() {
     setInput("var = [a, b]", "def function(name, deps, srcs):", "  v<caret>");
 
-    completeIfUnique();
+    editorTest.completeIfUnique();
 
     assertResult("var = [a, b]", "def function(name, deps, srcs):", "  var<caret>");
   }
@@ -50,7 +50,7 @@
   public void testLocalFunction() {
     setInput("def fnName():return True", "def function(name, deps, srcs):", "  fnN<caret>");
 
-    completeIfUnique();
+    editorTest.completeIfUnique();
 
     assertResult("def fnName():return True", "def function(name, deps, srcs):", "  fnName<caret>");
   }
@@ -59,7 +59,7 @@
   public void testNoCompletionAfterDot() {
     setInput("var = [a, b]", "def function(name, deps, srcs):", "  ext.v<caret>");
 
-    String[] completionItems = getCompletionItemsAsStrings();
+    String[] completionItems = editorTest.getCompletionItemsAsStrings();
     assertThat(completionItems).isEmpty();
   }
 
@@ -67,7 +67,7 @@
   public void testFunctionParam() {
     setInput("def test(var):", "  v<caret>");
 
-    completeIfUnique();
+    editorTest.completeIfUnique();
 
     assertResult("def test(var):", "  var<caret>");
   }
@@ -78,7 +78,7 @@
   public void testSymbolAssignedMultipleTimes() {
     setInput("var = 1", "var = 2", "var = 3", "<caret>");
 
-    completeIfUnique();
+    editorTest.completeIfUnique();
 
     assertResult("var = 1", "var = 2", "var = 3", "var<caret>");
   }
@@ -87,7 +87,7 @@
   public void testSymbolDefinedOutsideScope() {
     setInput("<caret>", "var = 1");
 
-    assertThat(getCompletionItemsAsStrings()).isEmpty();
+    assertThat(editorTest.getCompletionItemsAsStrings()).isEmpty();
   }
 
   @Test
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/ParameterCompletionContributorTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/ParameterCompletionContributorTest.java
index 4989580..6b8e85c 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/ParameterCompletionContributorTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/ParameterCompletionContributorTest.java
@@ -19,6 +19,7 @@
 
 import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.codeInsight.lookup.LookupElement;
 import com.intellij.openapi.editor.Editor;
 import org.junit.Test;
@@ -31,10 +32,10 @@
 
   @Test
   public void testArgsCompletion() {
-    BuildFile file = createBuildFile("BUILD", "def function(arg1, *");
+    BuildFile file = createBuildFile(new WorkspacePath("BUILD"), "def function(arg1, *");
 
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 0, "def function(arg1, *".length());
+    Editor editor = editorTest.openFileInEditor(file.getVirtualFile());
+    editorTest.setCaretPosition(editor, 0, "def function(arg1, *".length());
 
     LookupElement[] completionItems = testFixture.completeBasic();
     assertThat(completionItems).isNull();
@@ -44,10 +45,10 @@
 
   @Test
   public void testKwargsCompletion() {
-    BuildFile file = createBuildFile("BUILD", "def function(arg1, **");
+    BuildFile file = createBuildFile(new WorkspacePath("BUILD"), "def function(arg1, **");
 
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 0, "def function(arg1, **".length());
+    Editor editor = editorTest.openFileInEditor(file.getVirtualFile());
+    editorTest.setCaretPosition(editor, 0, "def function(arg1, **".length());
 
     LookupElement[] completionItems = testFixture.completeBasic();
     assertThat(completionItems).isNull();
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/RuleTargetCompletionTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/RuleTargetCompletionTest.java
index 2df632e..980da4d 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/RuleTargetCompletionTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/RuleTargetCompletionTest.java
@@ -19,6 +19,7 @@
 
 import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.codeInsight.lookup.LookupElement;
 import com.intellij.openapi.editor.Editor;
 import org.junit.Test;
@@ -33,14 +34,14 @@
   public void testLocalTarget() {
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "java_library(name = 'lib')",
             "java_library(",
             "    name = 'test',",
             "    deps = [':']");
 
-    Editor editor = openFileInEditor(file);
-    setCaretPosition(editor, 3, "    deps = [':".length());
+    Editor editor = editorTest.openFileInEditor(file);
+    editorTest.setCaretPosition(editor, 3, "    deps = [':".length());
 
     LookupElement[] completionItems = testFixture.completeBasic();
     assertThat(completionItems).hasLength(1);
@@ -51,10 +52,13 @@
   public void testIgnoreContainingTarget() {
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD", "java_library(", "    name = 'lib',", "    deps = [':']");
+            new WorkspacePath("java/com/google/BUILD"),
+            "java_library(",
+            "    name = 'lib',",
+            "    deps = [':']");
 
-    Editor editor = openFileInEditor(file);
-    setCaretPosition(editor, 2, "    deps = [':".length());
+    Editor editor = editorTest.openFileInEditor(file);
+    editorTest.setCaretPosition(editor, 2, "    deps = [':".length());
 
     LookupElement[] completionItems = testFixture.completeBasic();
     assertThat(completionItems).isEmpty();
@@ -64,52 +68,54 @@
   public void testNotCodeCompletionInNameField() {
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "java_library(name = 'lib')",
             "java_library(",
             "    name = 'l'",
             ")");
 
-    Editor editor = openFileInEditor(file);
-    setCaretPosition(editor, 2, "    name = 'l".length());
+    Editor editor = editorTest.openFileInEditor(file);
+    editorTest.setCaretPosition(editor, 2, "    name = 'l".length());
 
-    String[] completionItems = getCompletionItemsAsStrings();
+    String[] completionItems = editorTest.getCompletionItemsAsStrings();
     assertThat(completionItems).isEmpty();
   }
 
   @Test
   public void testNonLocalTarget() {
-    createBuildFile("java/com/google/foo/BUILD", "java_library(name = 'foo_lib')");
+    createBuildFile(
+        new WorkspacePath("java/com/google/foo/BUILD"), "java_library(name = 'foo_lib')");
 
     BuildFile bar =
         createBuildFile(
-            "java/com/google/bar/BUILD",
+            new WorkspacePath("java/com/google/bar/BUILD"),
             "java_library(",
             "    name = 'bar_lib',",
             "    deps = '//java/com/google/foo:')");
 
-    Editor editor = openFileInEditor(bar);
-    setCaretPosition(editor, 2, "    deps = '//java/com/google/foo:".length());
+    Editor editor = editorTest.openFileInEditor(bar);
+    editorTest.setCaretPosition(editor, 2, "    deps = '//java/com/google/foo:".length());
 
-    String[] completionItems = getCompletionItemsAsStrings();
+    String[] completionItems = editorTest.getCompletionItemsAsStrings();
     assertThat(completionItems).asList().containsExactly("'//java/com/google/foo:foo_lib'");
   }
 
   @Test
   public void testNonLocalRulesNotCompletedWithoutColon() {
-    createBuildFile("java/com/google/foo/BUILD", "java_library(name = 'foo_lib')");
+    createBuildFile(
+        new WorkspacePath("java/com/google/foo/BUILD"), "java_library(name = 'foo_lib')");
 
     BuildFile bar =
         createBuildFile(
-            "java/com/google/bar/BUILD",
+            new WorkspacePath("java/com/google/bar/BUILD"),
             "java_library(",
             "    name = 'bar_lib',",
             "    deps = '//java/com/google/foo')");
 
-    Editor editor = openFileInEditor(bar);
-    setCaretPosition(editor, 2, "    deps = '//java/com/google/foo".length());
+    Editor editor = editorTest.openFileInEditor(bar);
+    editorTest.setCaretPosition(editor, 2, "    deps = '//java/com/google/foo".length());
 
-    String[] completionItems = getCompletionItemsAsStrings();
+    String[] completionItems = editorTest.getCompletionItemsAsStrings();
     assertThat(completionItems).isEmpty();
   }
 
@@ -117,16 +123,16 @@
   public void testPackageLocalRulesCompletedWithoutColon() {
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "java_library(name = 'lib')",
             "java_library(",
             "    name = 'test',",
             "    deps = ['']");
 
-    Editor editor = openFileInEditor(file);
-    setCaretPosition(editor, 3, "    deps = ['".length());
+    Editor editor = editorTest.openFileInEditor(file);
+    editorTest.setCaretPosition(editor, 3, "    deps = ['".length());
 
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(
         file,
         "java_library(name = 'lib')",
@@ -137,20 +143,20 @@
 
   @Test
   public void testLocalPathIgnoredForNonLocalLabels() {
-    createBuildFile("java/BUILD", "java_library(name = 'root_rule')");
+    createBuildFile(new WorkspacePath("java/BUILD"), "java_library(name = 'root_rule')");
 
     BuildFile otherPackage =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "java_library(",
             "java_library(name = 'other_rule')",
             "    name = 'lib',",
             "    deps = ['//java:']");
 
-    Editor editor = openFileInEditor(otherPackage);
-    setCaretPosition(editor, 3, "    deps = ['//java:".length());
+    Editor editor = editorTest.openFileInEditor(otherPackage);
+    editorTest.setCaretPosition(editor, 3, "    deps = ['//java:".length());
 
-    String[] completionItems = getCompletionItemsAsStrings();
+    String[] completionItems = editorTest.getCompletionItemsAsStrings();
     assertThat(completionItems).asList().contains("'//java:root_rule'");
     assertThat(completionItems).asList().doesNotContain("'//java/com/google:other_rule'");
   }
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/SkylarkExtensionCompletionTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/SkylarkExtensionCompletionTest.java
index f402723..397abbb 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/SkylarkExtensionCompletionTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/SkylarkExtensionCompletionTest.java
@@ -18,6 +18,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.openapi.vfs.VirtualFile;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -27,113 +28,123 @@
 @RunWith(JUnit4.class)
 public class SkylarkExtensionCompletionTest extends BuildFileIntegrationTestCase {
 
-  private VirtualFile createAndSetCaret(String filePath, String... fileContents) {
-    VirtualFile file = createFile(filePath, fileContents);
+  private VirtualFile createAndSetCaret(WorkspacePath workspacePath, String... fileContents) {
+    VirtualFile file = workspace.createFile(workspacePath, fileContents);
     testFixture.configureFromExistingVirtualFile(file);
     return file;
   }
 
   @Test
   public void testSimpleCase() {
-    createFile("skylark.bzl");
-    VirtualFile file = createAndSetCaret("BUILD", "load(':<caret>'");
+    workspace.createFile(new WorkspacePath("skylark.bzl"));
+    VirtualFile file = createAndSetCaret(new WorkspacePath("BUILD"), "load(':<caret>'");
 
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "load(':skylark.bzl'");
   }
 
   @Test
   public void testSelfNotInResults() {
-    createFile("BUILD");
-    createAndSetCaret("self.bzl", "load(':<caret>'");
+    workspace.createFile(new WorkspacePath("BUILD"));
+    createAndSetCaret(new WorkspacePath("self.bzl"), "load(':<caret>'");
 
     assertThat(testFixture.completeBasic()).isEmpty();
   }
 
   @Test
   public void testSelfNotInResults2() {
-    createFile("skylark.bzl");
-    createFile("BUILD");
-    VirtualFile file = createAndSetCaret("self.bzl", "load(':<caret>'");
+    workspace.createFile(new WorkspacePath("skylark.bzl"));
+    workspace.createFile(new WorkspacePath("BUILD"));
+    VirtualFile file = createAndSetCaret(new WorkspacePath("self.bzl"), "load(':<caret>'");
 
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "load(':skylark.bzl'");
   }
 
   @Test
   public void testNoRulesInResults() {
-    createFile("java/com/google/foo/skylark.bzl");
-    createFile("java/com/google/foo/BUILD", "java_library(name = 'foo')");
+    workspace.createFile(new WorkspacePath("java/com/google/foo/skylark.bzl"));
+    workspace.createFile(
+        new WorkspacePath("java/com/google/foo/BUILD"), "java_library(name = 'foo')");
     VirtualFile file =
-        createAndSetCaret("java/com/google/bar/BUILD", "load('//java/com/google/foo:<caret>'");
+        createAndSetCaret(
+            new WorkspacePath("java/com/google/bar/BUILD"), "load('//java/com/google/foo:<caret>'");
 
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "load('//java/com/google/foo:skylark.bzl'");
 
     // now check that the rule would have been picked up outside of the 'load' context
-    file = createAndSetCaret("java/com/google/baz/BUILD", "'//java/com/google/foo:<caret>'");
+    file =
+        createAndSetCaret(
+            new WorkspacePath("java/com/google/baz/BUILD"), "'//java/com/google/foo:<caret>'");
 
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "'//java/com/google/foo:foo'");
   }
 
   @Test
   public void testNonSkylarkFilesNotInResults() {
-    createFile("java/com/google/foo/text.txt");
+    workspace.createFile(new WorkspacePath("java/com/google/foo/text.txt"));
 
-    createAndSetCaret("java/com/google/bar/BUILD", "load('//java/com/google/foo:<caret>'");
+    createAndSetCaret(
+        new WorkspacePath("java/com/google/bar/BUILD"), "load('//java/com/google/foo:<caret>'");
 
     assertThat(testFixture.completeBasic()).isEmpty();
   }
 
   @Test
   public void testLabelStartsWithColon() {
-    createFile("java/com/google/skylark.bzl");
-    VirtualFile file = createAndSetCaret("java/com/google/BUILD", "load(':<caret>'");
+    workspace.createFile(new WorkspacePath("java/com/google/skylark.bzl"));
+    VirtualFile file =
+        createAndSetCaret(new WorkspacePath("java/com/google/BUILD"), "load(':<caret>'");
 
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "load(':skylark.bzl'");
   }
 
   @Test
   public void testLabelStartsWithSlashes() {
-    createFile("java/com/google/skylark.bzl");
+    workspace.createFile(new WorkspacePath("java/com/google/skylark.bzl"));
     VirtualFile file =
-        createAndSetCaret("java/com/google/BUILD", "load('//java/com/google:<caret>'");
+        createAndSetCaret(
+            new WorkspacePath("java/com/google/BUILD"), "load('//java/com/google:<caret>'");
 
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "load('//java/com/google:skylark.bzl'");
   }
 
   @Test
   public void testLabelStartsWithSlashesWithoutColon() {
-    createFile("java/com/google/skylark.bzl");
+    workspace.createFile(new WorkspacePath("java/com/google/skylark.bzl"));
     VirtualFile file =
-        createAndSetCaret("java/com/google/BUILD", "load('//java/com/google<caret>'");
+        createAndSetCaret(
+            new WorkspacePath("java/com/google/BUILD"), "load('//java/com/google<caret>'");
 
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "load('//java/com/google:skylark.bzl'");
   }
 
   @Test
   public void testDirectoryCompletionInLoadStatement() {
-    createFile("java/com/google/skylark.bzl");
-    VirtualFile file = createAndSetCaret("java/com/google/BUILD", "load('//<caret>'");
+    workspace.createFile(new WorkspacePath("java/com/google/skylark.bzl"));
+    VirtualFile file =
+        createAndSetCaret(new WorkspacePath("java/com/google/BUILD"), "load('//<caret>'");
 
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "load('//java/com/google'");
 
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "load('//java/com/google:skylark.bzl'");
   }
 
   @Test
   public void testMultipleFiles() {
-    createFile("java/com/google/skylark.bzl");
-    createFile("java/com/google/other.bzl");
-    createAndSetCaret("java/com/google/BUILD", "load('//java/com/google:<caret>'");
+    workspace.createFile(new WorkspacePath("java/com/google/skylark.bzl"));
+    workspace.createFile(new WorkspacePath("java/com/google/other.bzl"));
+    createAndSetCaret(
+        new WorkspacePath("java/com/google/BUILD"), "load('//java/com/google:<caret>'");
 
-    String[] strings = getCompletionItemsAsStrings();
+    String[] strings = editorTest.getCompletionItemsAsStrings();
     assertThat(strings).hasLength(2);
     assertThat(strings)
         .asList()
@@ -144,12 +155,16 @@
   // are relative to the parent blaze package directory
   @Test
   public void testRelativePathInSubdirectory() {
-    createFile("java/com/google/BUILD");
-    createFile("java/com/google/nonPackageSubdirectory/skylark.bzl", "def function(): return");
+    workspace.createFile(new WorkspacePath("java/com/google/BUILD"));
+    workspace.createFile(
+        new WorkspacePath("java/com/google/nonPackageSubdirectory/skylark.bzl"),
+        "def function(): return");
     VirtualFile file =
-        createAndSetCaret("java/com/google/nonPackageSubdirectory/other.bzl", "load(':n<caret>'");
+        createAndSetCaret(
+            new WorkspacePath("java/com/google/nonPackageSubdirectory/other.bzl"),
+            "load(':n<caret>'");
 
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "load(':nonPackageSubdirectory/skylark.bzl'");
   }
 }
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/SkylarkExtensionSymbolCompletionTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/SkylarkExtensionSymbolCompletionTest.java
index 4f85d97..44f3cb9 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/SkylarkExtensionSymbolCompletionTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/completion/SkylarkExtensionSymbolCompletionTest.java
@@ -18,6 +18,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.openapi.vfs.VirtualFile;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -27,64 +28,72 @@
 @RunWith(JUnit4.class)
 public class SkylarkExtensionSymbolCompletionTest extends BuildFileIntegrationTestCase {
 
-  private VirtualFile createAndSetCaret(String filePath, String... fileContents) {
-    VirtualFile file = createFile(filePath, fileContents);
+  private VirtualFile createAndSetCaret(WorkspacePath workspacePath, String... fileContents) {
+    VirtualFile file = workspace.createFile(workspacePath, fileContents);
     testFixture.configureFromExistingVirtualFile(file);
     return file;
   }
 
   @Test
   public void testGlobalVariable() {
-    createFile("skylark.bzl", "VAR = []");
-    VirtualFile file = createAndSetCaret("BUILD", "load(':skylark.bzl', '<caret>')");
+    workspace.createFile(new WorkspacePath("skylark.bzl"), "VAR = []");
+    VirtualFile file =
+        createAndSetCaret(new WorkspacePath("BUILD"), "load(':skylark.bzl', '<caret>')");
 
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "load(':skylark.bzl', 'VAR')");
   }
 
   @Test
   public void testFunctionStatement() {
-    createFile("skylark.bzl", "def fn(param):stmt");
-    VirtualFile file = createAndSetCaret("BUILD", "load(':skylark.bzl', '<caret>')");
+    workspace.createFile(new WorkspacePath("skylark.bzl"), "def fn(param):stmt");
+    VirtualFile file =
+        createAndSetCaret(new WorkspacePath("BUILD"), "load(':skylark.bzl', '<caret>')");
 
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "load(':skylark.bzl', 'fn')");
   }
 
   @Test
   public void testMultipleOptions() {
-    createFile("skylark.bzl", "def fn(param):stmt", "VAR = []");
-    createAndSetCaret("BUILD", "load(':skylark.bzl', '<caret>')");
+    workspace.createFile(new WorkspacePath("skylark.bzl"), "def fn(param):stmt", "VAR = []");
+    createAndSetCaret(new WorkspacePath("BUILD"), "load(':skylark.bzl', '<caret>')");
 
-    String[] options = getCompletionItemsAsStrings();
+    String[] options = editorTest.getCompletionItemsAsStrings();
     assertThat(options).asList().containsExactly("'fn'", "'VAR'");
   }
 
   @Test
   public void testRulesNotIncluded() {
-    createFile("skylark.bzl", "java_library(name = 'lib')", "native.java_library(name = 'foo'");
-    createAndSetCaret("BUILD", "load(':skylark.bzl', '<caret>')");
+    workspace.createFile(
+        new WorkspacePath("skylark.bzl"),
+        "java_library(name = 'lib')",
+        "native.java_library(name = 'foo'");
+    createAndSetCaret(new WorkspacePath("BUILD"), "load(':skylark.bzl', '<caret>')");
 
     assertThat(testFixture.completeBasic()).isEmpty();
   }
 
   @Test
   public void testLoadedSymbols() {
-    createFile("other.bzl", "def function()");
-    createFile("skylark.bzl", "load(':other.bzl', 'function')");
-    VirtualFile file = createAndSetCaret("BUILD", "load(':skylark.bzl', '<caret>')");
+    workspace.createFile(new WorkspacePath("other.bzl"), "def function()");
+    workspace.createFile(new WorkspacePath("skylark.bzl"), "load(':other.bzl', 'function')");
+    VirtualFile file =
+        createAndSetCaret(new WorkspacePath("BUILD"), "load(':skylark.bzl', '<caret>')");
 
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "load(':skylark.bzl', 'function')");
   }
 
   @Test
   public void testNotLoadedSymbolsAreNotIncluded() {
-    createFile("other.bzl", "def function():stmt", "def other_function():stmt");
-    createFile("skylark.bzl", "load(':other.bzl', 'function')");
-    VirtualFile file = createAndSetCaret("BUILD", "load(':skylark.bzl', '<caret>')");
+    workspace.createFile(
+        new WorkspacePath("other.bzl"), "def function():stmt", "def other_function():stmt");
+    workspace.createFile(new WorkspacePath("skylark.bzl"), "load(':other.bzl', 'function')");
+    VirtualFile file =
+        createAndSetCaret(new WorkspacePath("BUILD"), "load(':skylark.bzl', '<caret>')");
 
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertFileContents(file, "load(':skylark.bzl', 'function')");
   }
 }
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildBraceMatcherTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildBraceMatcherTest.java
index c4af1d4..308e5dd 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildBraceMatcherTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildBraceMatcherTest.java
@@ -34,7 +34,7 @@
   public void testClosingParenInserted() {
     PsiFile file = setInput("java_library<caret>");
 
-    performTypingAction(testFixture.getEditor(), '(');
+    editorTest.performTypingAction(testFixture.getEditor(), '(');
 
     assertFileContents(file, "java_library()");
   }
@@ -43,7 +43,7 @@
   public void testClosingBraceInserted() {
     PsiFile file = setInput("<caret>");
 
-    performTypingAction(testFixture.getEditor(), '{');
+    editorTest.performTypingAction(testFixture.getEditor(), '{');
 
     assertFileContents(file, "{}");
   }
@@ -52,7 +52,7 @@
   public void testClosingBracketInserted() {
     PsiFile file = setInput("<caret>");
 
-    performTypingAction(testFixture.getEditor(), '[');
+    editorTest.performTypingAction(testFixture.getEditor(), '[');
 
     assertFileContents(file, "[]");
   }
@@ -61,7 +61,7 @@
   public void testNoClosingBracketInsertedIfLaterDanglingRBracket() {
     PsiFile file = setInput("java_library(", "    srcs =<caret> 'source.java']", ")");
 
-    performTypingAction(testFixture.getEditor(), '[');
+    editorTest.performTypingAction(testFixture.getEditor(), '[');
 
     assertFileContents(file, "java_library(", "    srcs =[ 'source.java']", ")");
   }
@@ -70,7 +70,7 @@
   public void testClosingBracketInsertedIfFollowedByWhitespace() {
     PsiFile file = setInput("java_library(", "    srcs =<caret> 'source.java'", ")");
 
-    performTypingAction(testFixture.getEditor(), '[');
+    editorTest.performTypingAction(testFixture.getEditor(), '[');
 
     assertFileContents(file, "java_library(", "    srcs =[] 'source.java'", ")");
   }
@@ -79,19 +79,19 @@
   public void testNoClosingBraceInsertedWhenFollowedByIdentifier() {
     PsiFile file = setInput("hello = <caret>test");
 
-    performTypingAction(testFixture.getEditor(), '(');
+    editorTest.performTypingAction(testFixture.getEditor(), '(');
 
     assertFileContents(file, "hello = (test");
 
     file = setInput("hello = <caret>test");
 
-    performTypingAction(testFixture.getEditor(), '[');
+    editorTest.performTypingAction(testFixture.getEditor(), '[');
 
     assertFileContents(file, "hello = [test");
 
     file = setInput("hello = <caret>test");
 
-    performTypingAction(testFixture.getEditor(), '{');
+    editorTest.performTypingAction(testFixture.getEditor(), '{');
 
     assertFileContents(file, "hello = {test");
   }
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildIndentOnEnterTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildIndentOnEnterTest.java
index 684255a..9915e25 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildIndentOnEnterTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildIndentOnEnterTest.java
@@ -31,7 +31,7 @@
   }
 
   private void pressEnterAndAssertResult(String... resultingFileContents) {
-    pressButton(IdeActions.ACTION_EDITOR_ENTER);
+    editorTest.pressButton(IdeActions.ACTION_EDITOR_ENTER);
     testFixture.getFile().getText();
     testFixture.checkResult(Joiner.on("\n").join(resultingFileContents));
   }
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildQuoteHandlerTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildQuoteHandlerTest.java
index 693ea1b..214d775 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildQuoteHandlerTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/BuildQuoteHandlerTest.java
@@ -18,6 +18,7 @@
 import com.google.common.base.Joiner;
 import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -28,88 +29,90 @@
 
   @Test
   public void testClosingQuoteInserted() {
-    BuildFile file = createBuildFile("BUILD", "");
+    BuildFile file = createBuildFile(new WorkspacePath("BUILD"), "");
 
-    performTypingAction(file, '"');
+    editorTest.performTypingAction(file, '"');
     assertFileContents(file, "\"\"");
   }
 
   @Test
   public void testClosingSingleQuoteInserted() {
-    BuildFile file = createBuildFile("BUILD", "");
+    BuildFile file = createBuildFile(new WorkspacePath("BUILD"), "");
 
-    performTypingAction(file, '\'');
+    editorTest.performTypingAction(file, '\'');
     assertFileContents(file, "''");
   }
 
   @Test
   public void testClosingTripleQuoteInserted() {
-    BuildFile file = createBuildFile("BUILD", "");
+    BuildFile file = createBuildFile(new WorkspacePath("BUILD"), "");
 
-    performTypingAction(file, '"');
-    performTypingAction(file, '"');
-    performTypingAction(file, '"');
+    editorTest.performTypingAction(file, '"');
+    editorTest.performTypingAction(file, '"');
+    editorTest.performTypingAction(file, '"');
     assertFileContents(file, "\"\"\"\"\"\"");
   }
 
   @Test
   public void testClosingTripleSingleQuoteInserted() {
-    BuildFile file = createBuildFile("BUILD", "");
+    BuildFile file = createBuildFile(new WorkspacePath("BUILD"), "");
 
-    performTypingAction(file, '\'');
-    performTypingAction(file, '\'');
-    performTypingAction(file, '\'');
+    editorTest.performTypingAction(file, '\'');
+    editorTest.performTypingAction(file, '\'');
+    editorTest.performTypingAction(file, '\'');
     assertFileContents(file, "''''''");
   }
 
   @Test
   public void testOnlyCaretMovedWhenCompletingExistingClosingQuotes() {
-    BuildFile file = createBuildFile("BUILD", "'text<caret>'", "laterContents");
+    BuildFile file = createBuildFile(new WorkspacePath("BUILD"), "'text<caret>'", "laterContents");
 
     testFixture.configureFromExistingVirtualFile(file.getVirtualFile());
 
-    performTypingAction(file, '\'');
+    editorTest.performTypingAction(file, '\'');
 
     testFixture.checkResult(Joiner.on("\n").join("'text'<caret>", "laterContents"));
   }
 
   @Test
   public void testOnlyCaretMovedWhenCompletingExistingClosingTripleQuotes() {
-    BuildFile file = createBuildFile("BUILD", "'''text<caret>'''", "laterContents");
+    BuildFile file =
+        createBuildFile(new WorkspacePath("BUILD"), "'''text<caret>'''", "laterContents");
 
     testFixture.configureFromExistingVirtualFile(file.getVirtualFile());
 
-    performTypingAction(file, '\'');
+    editorTest.performTypingAction(file, '\'');
 
     testFixture.checkResult(Joiner.on("\n").join("'''text'<caret>''", "laterContents"));
 
-    performTypingAction(file, '\'');
+    editorTest.performTypingAction(file, '\'');
 
     testFixture.checkResult(Joiner.on("\n").join("'''text''<caret>'", "laterContents"));
 
-    performTypingAction(file, '\'');
+    editorTest.performTypingAction(file, '\'');
 
     testFixture.checkResult(Joiner.on("\n").join("'''text'''<caret>", "laterContents"));
   }
 
   @Test
   public void testAdditionalTripleQuotesNotInsertedWhenClosingQuotes() {
-    BuildFile file = createBuildFile("BUILD", "'''text''<caret>", "laterContents");
+    BuildFile file =
+        createBuildFile(new WorkspacePath("BUILD"), "'''text''<caret>", "laterContents");
 
     testFixture.configureFromExistingVirtualFile(file.getVirtualFile());
 
-    performTypingAction(file, '\'');
+    editorTest.performTypingAction(file, '\'');
 
     testFixture.checkResult(Joiner.on("\n").join("'''text'''<caret>", "laterContents"));
   }
 
   @Test
   public void testAdditionalQuoteNotInsertedWhenClosingQuotes() {
-    BuildFile file = createBuildFile("BUILD", "'text<caret>", "laterContents");
+    BuildFile file = createBuildFile(new WorkspacePath("BUILD"), "'text<caret>", "laterContents");
 
     testFixture.configureFromExistingVirtualFile(file.getVirtualFile());
 
-    performTypingAction(file, '\'');
+    editorTest.performTypingAction(file, '\'');
 
     testFixture.checkResult(Joiner.on("\n").join("'text'<caret>", "laterContents"));
   }
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/EnterInLineCommentTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/EnterInLineCommentTest.java
index 2f3b0f2..abb47d1 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/EnterInLineCommentTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/editor/EnterInLineCommentTest.java
@@ -17,6 +17,7 @@
 
 import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.openapi.editor.Editor;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -28,23 +29,27 @@
 
   @Test
   public void testInternalNewlineCommented() {
-    BuildFile file = createBuildFile("BUILD", "# first line comment", "# second line comment");
+    BuildFile file =
+        createBuildFile(
+            new WorkspacePath("BUILD"), "# first line comment", "# second line comment");
 
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 1, "# second ".length());
-    performTypingAction(editor, '\n');
+    Editor editor = editorTest.openFileInEditor(file.getVirtualFile());
+    editorTest.setCaretPosition(editor, 1, "# second ".length());
+    editorTest.performTypingAction(editor, '\n');
     assertFileContents(file, "# first line comment", "# second ", "# line comment");
-    assertCaretPosition(editor, 2, 2);
+    editorTest.assertCaretPosition(editor, 2, 2);
   }
 
   @Test
   public void testNewlineAtEndOfComment() {
-    BuildFile file = createBuildFile("BUILD", "# first line comment", "# second line comment");
+    BuildFile file =
+        createBuildFile(
+            new WorkspacePath("BUILD"), "# first line comment", "# second line comment");
 
-    Editor editor = openFileInEditor(file.getVirtualFile());
-    setCaretPosition(editor, 1, "# second line comment".length());
-    performTypingAction(editor, '\n');
+    Editor editor = editorTest.openFileInEditor(file.getVirtualFile());
+    editorTest.setCaretPosition(editor, 1, "# second line comment".length());
+    editorTest.performTypingAction(editor, '\n');
     assertFileContents(file, "# first line comment", "# second line comment", "");
-    assertCaretPosition(editor, 2, 0);
+    editorTest.assertCaretPosition(editor, 2, 0);
   }
 }
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/BlazePackageFindUsagesTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/BlazePackageFindUsagesTest.java
index f7a52f0..844bfe6 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/BlazePackageFindUsagesTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/BlazePackageFindUsagesTest.java
@@ -21,6 +21,7 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
 import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
 import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiReference;
 import org.junit.Test;
@@ -36,11 +37,11 @@
 
   @Test
   public void testDirectReferenceFound() {
-    BuildFile foo = createBuildFile("java/com/google/foo/BUILD");
+    BuildFile foo = createBuildFile(new WorkspacePath("java/com/google/foo/BUILD"));
 
     BuildFile bar =
         createBuildFile(
-            "java/com/google/bar/BUILD",
+            new WorkspacePath("java/com/google/bar/BUILD"),
             "package_group(name = \"grp\", packages = [\"//java/com/google/foo\"])");
 
     PsiReference[] references = FindUsages.findAllReferences(foo);
@@ -53,11 +54,13 @@
 
   @Test
   public void testLabelFragmentReferenceFound() {
-    BuildFile foo = createBuildFile("java/com/google/foo/BUILD", "java_library(name = \"lib\")");
+    BuildFile foo =
+        createBuildFile(
+            new WorkspacePath("java/com/google/foo/BUILD"), "java_library(name = \"lib\")");
 
     BuildFile bar =
         createBuildFile(
-            "java/com/google/bar/BUILD",
+            new WorkspacePath("java/com/google/bar/BUILD"),
             "java_library(name = \"lib2\", exports = [\"//java/com/google/foo:lib\"])");
 
     PsiReference[] references = FindUsages.findAllReferences(foo);
@@ -73,7 +76,7 @@
   public void testInternalReferencesResolve() {
     BuildFile buildFile =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "java_library(name = \"lib\")",
             "java_library(name = \"other\", deps = [\"//java/com/google:lib\"])");
 
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/ExternalFileUsagesTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/ExternalFileUsagesTest.java
index cdb0c64..b7a8392 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/ExternalFileUsagesTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/ExternalFileUsagesTest.java
@@ -24,6 +24,7 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
 import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
 import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
 import com.intellij.psi.PsiReference;
@@ -41,14 +42,15 @@
   @Test
   public void testJavaClassUsagesFound() {
     PsiFile javaFile =
-        createPsiFile(
-            "com/google/foo/JavaClass.java",
+        workspace.createPsiFile(
+            new WorkspacePath("com/google/foo/JavaClass.java"),
             "package com.google.foo;",
             "public class JavaClass {}");
 
     BuildFile buildFile =
         createBuildFile(
-            "com/google/foo/BUILD", "java_library(name = \"lib\", srcs = [\"JavaClass.java\"])");
+            new WorkspacePath("com/google/foo/BUILD"),
+            "java_library(name = \"lib\", srcs = [\"JavaClass.java\"])");
 
     PsiReference[] references = FindUsages.findAllReferences(javaFile);
     assertThat(references).hasLength(1);
@@ -63,10 +65,10 @@
 
   @Test
   public void testTextFileUsagesFound() {
-    PsiFile textFile = createPsiFile("com/google/foo/data.txt");
+    PsiFile textFile = workspace.createPsiFile(new WorkspacePath("com/google/foo/data.txt"));
 
     createBuildFile(
-        "com/google/foo/BUILD",
+        new WorkspacePath("com/google/foo/BUILD"),
         "filegroup(name = \"lib\", srcs = [\"data.txt\"])",
         "filegroup(name = \"lib2\", srcs = [\"//com/google/foo:data.txt\"])");
 
@@ -76,10 +78,12 @@
 
   @Test
   public void testInvalidReferenceDoesntResolve() {
-    createBuildFile("com/google/foo/BUILD");
-    PsiFile textFileInFoo = createPsiFile("com/google/foo/data.txt");
+    createBuildFile(new WorkspacePath("com/google/foo/BUILD"));
+    PsiFile textFileInFoo = workspace.createPsiFile(new WorkspacePath("com/google/foo/data.txt"));
 
-    createBuildFile("com/google/bar/BUILD", "filegroup(name = \"lib\", srcs = [\":data.txt\"])");
+    createBuildFile(
+        new WorkspacePath("com/google/bar/BUILD"),
+        "filegroup(name = \"lib\", srcs = [\":data.txt\"])");
 
     PsiReference[] references = FindUsages.findAllReferences(textFileInFoo);
     assertThat(references).isEmpty();
@@ -87,9 +91,10 @@
 
   @Test
   public void testSkylarkExtensionUsagesFound() {
-    BuildFile ext = createBuildFile("com/google/foo/ext.bzl", "def fn(): return");
+    BuildFile ext =
+        createBuildFile(new WorkspacePath("com/google/foo/ext.bzl"), "def fn(): return");
     createBuildFile(
-        "com/google/foo/BUILD",
+        new WorkspacePath("com/google/foo/BUILD"),
         "load(':ext.bzl', 'fn')",
         "load('ext.bzl', 'fn')",
         "load('//com/google/foo:ext.bzl', 'fn')");
@@ -100,9 +105,10 @@
 
   @Test
   public void testSkylarkExtensionInSubDirectoryUsagesFound() {
-    BuildFile ext = createBuildFile("com/google/foo/subdir/ext.bzl", "def fn(): return");
+    BuildFile ext =
+        createBuildFile(new WorkspacePath("com/google/foo/subdir/ext.bzl"), "def fn(): return");
     createBuildFile(
-        "com/google/foo/BUILD",
+        new WorkspacePath("com/google/foo/BUILD"),
         "load(':subdir/ext.bzl', 'fn')",
         "load('subdir/ext.bzl', 'fn')",
         "load('//com/google/foo:subdir/ext.bzl', 'fn')");
@@ -113,10 +119,12 @@
 
   @Test
   public void testSkylarkExtensionInSubDirectoryOfDifferentPackage() {
-    createBuildFile("com/google/foo/BUILD");
-    BuildFile ext = createBuildFile("com/google/foo/subdir/ext.bzl", "def fn(): return");
+    createBuildFile(new WorkspacePath("com/google/foo/BUILD"));
+    BuildFile ext =
+        createBuildFile(new WorkspacePath("com/google/foo/subdir/ext.bzl"), "def fn(): return");
 
-    createBuildFile("com/google/bar/BUILD", "load('//com/google/foo:subdir/ext.bzl', 'fn')");
+    createBuildFile(
+        new WorkspacePath("com/google/bar/BUILD"), "load('//com/google/foo:subdir/ext.bzl', 'fn')");
 
     PsiReference[] references = FindUsages.findAllReferences(ext);
     assertThat(references).hasLength(1);
@@ -124,9 +132,11 @@
 
   @Test
   public void testSkylarkExtensionReferencedFromSubpackage() {
-    createBuildFile("com/google/foo/BUILD");
-    BuildFile ext1 = createBuildFile("com/google/foo/subdir/testing.bzl", "def fn(): return");
-    createBuildFile("com/google/foo/subdir/other.bzl", "load(':subdir/testing.bzl', 'fn')");
+    createBuildFile(new WorkspacePath("com/google/foo/BUILD"));
+    BuildFile ext1 =
+        createBuildFile(new WorkspacePath("com/google/foo/subdir/testing.bzl"), "def fn(): return");
+    createBuildFile(
+        new WorkspacePath("com/google/foo/subdir/other.bzl"), "load(':subdir/testing.bzl', 'fn')");
 
     PsiReference[] references = FindUsages.findAllReferences(ext1);
     assertThat(references).hasLength(1);
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FindParameterUsagesTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FindParameterUsagesTest.java
index 8928bef..3c4d694 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FindParameterUsagesTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FindParameterUsagesTest.java
@@ -22,6 +22,7 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.FunctionStatement;
 import com.google.idea.blaze.base.lang.buildfile.psi.ParameterList;
 import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.psi.PsiReference;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -37,7 +38,7 @@
   public void testLocalReferences() {
     BuildFile buildFile =
         createBuildFile(
-            "java/com/google/build_defs.bzl",
+            new WorkspacePath("java/com/google/build_defs.bzl"),
             "def function(arg1, arg2)",
             "function(arg1 = 1, arg2 = \"name\")");
 
@@ -53,11 +54,13 @@
 
   @Test
   public void testNonLocalReferences() {
-    BuildFile foo = createBuildFile("java/com/google/build_defs.bzl", "def function(arg1, arg2)");
+    BuildFile foo =
+        createBuildFile(
+            new WorkspacePath("java/com/google/build_defs.bzl"), "def function(arg1, arg2)");
 
     BuildFile bar =
         createBuildFile(
-            "java/com/google/other/BUILD",
+            new WorkspacePath("java/com/google/other/BUILD"),
             "load(\"//java/com/google:build_defs.bzl\", \"function\")",
             "function(arg1 = 1, arg2 = \"name\", extra = x)");
 
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FindRuleUsagesTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FindRuleUsagesTest.java
index 1e81970..6118f88 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FindRuleUsagesTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FindRuleUsagesTest.java
@@ -24,6 +24,7 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.ListLiteral;
 import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
 import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiReference;
@@ -39,7 +40,7 @@
   public void testLocalReferences() {
     BuildFile buildFile =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "java_library(name = \"target\")",
             "top_level_ref = \":target\"",
             "java_library(name = \"other\", deps = [\":target\"]");
@@ -63,7 +64,7 @@
   public void testLocalFullReference() {
     BuildFile buildFile =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "java_library(name = \"target\")",
             "java_library(name = \"other\", deps = [\"//java/com/google:target\"]");
 
@@ -80,11 +81,12 @@
   @Test
   public void testNonLocalReferences() {
     BuildFile targetFile =
-        createBuildFile("java/com/google/foo/BUILD", "java_library(name = \"target\")");
+        createBuildFile(
+            new WorkspacePath("java/com/google/foo/BUILD"), "java_library(name = \"target\")");
 
     BuildFile refFile =
         createBuildFile(
-            "java/com/google/bar/BUILD",
+            new WorkspacePath("java/com/google/bar/BUILD"),
             "java_library(name = \"ref\", exports = [\"//java/com/google/foo:target\"])");
 
     FuncallExpression target = targetFile.findChildByClass(FuncallExpression.class);
@@ -100,11 +102,13 @@
   @Test
   public void testFindUsagesWorksFromNameString() {
     BuildFile targetFile =
-        createBuildFile("java/com/google/foo/BUILD", "java_library(name = \"tar<caret>get\")");
+        createBuildFile(
+            new WorkspacePath("java/com/google/foo/BUILD"),
+            "java_library(name = \"tar<caret>get\")");
 
     BuildFile refFile =
         createBuildFile(
-            "java/com/google/bar/BUILD",
+            new WorkspacePath("java/com/google/bar/BUILD"),
             "java_library(name = \"ref\", exports = [\"//java/com/google/foo:target\"])");
 
     testFixture.configureFromExistingVirtualFile(targetFile.getVirtualFile());
@@ -125,10 +129,12 @@
   public void testInvalidReferenceDoesntResolve() {
     // reference ":target" from another build file (missing package path in label)
     BuildFile targetFile =
-        createBuildFile("java/com/google/foo/BUILD", "java_library(name = \"target\")");
+        createBuildFile(
+            new WorkspacePath("java/com/google/foo/BUILD"), "java_library(name = \"target\")");
 
     createBuildFile(
-        "java/com/google/bar/BUILD", "java_library(name = \"ref\", exports = [\":target\"])");
+        new WorkspacePath("java/com/google/bar/BUILD"),
+        "java_library(name = \"ref\", exports = [\":target\"])");
 
     FuncallExpression target = targetFile.findChildByClass(FuncallExpression.class);
     assertThat(target).isNotNull();
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FunctionStatementUsagesTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FunctionStatementUsagesTest.java
index b03f32d..9197feb 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FunctionStatementUsagesTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/FunctionStatementUsagesTest.java
@@ -24,6 +24,7 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.LoadStatement;
 import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
 import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiReference;
 import org.junit.Test;
@@ -38,7 +39,7 @@
   public void testLocalReferences() {
     BuildFile buildFile =
         createBuildFile(
-            "java/com/google/build_defs.bzl",
+            new WorkspacePath("java/com/google/build_defs.bzl"),
             "def function(name, srcs, deps):",
             "    # function body",
             "function(name = \"foo\")");
@@ -55,11 +56,12 @@
   @Test
   public void testLoadedFunctionReferences() {
     BuildFile extFile =
-        createBuildFile("java/com/google/build_defs.bzl", "def function(name, deps)");
+        createBuildFile(
+            new WorkspacePath("java/com/google/build_defs.bzl"), "def function(name, deps)");
 
     BuildFile buildFile =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "load(",
             "\"//java/com/google:build_defs.bzl\",",
             "\"function\"",
@@ -73,17 +75,18 @@
 
     PsiElement ref = references[0].getElement();
     assertThat(ref).isInstanceOf(StringLiteral.class);
-    assertThat(ref.getParent()).isEqualTo(load);
+    assertThat(ref.getParent().getParent()).isEqualTo(load);
   }
 
   @Test
   public void testFuncallReference() {
     BuildFile extFile =
-        createBuildFile("java/com/google/tools/build_defs.bzl", "def function(name, deps)");
+        createBuildFile(
+            new WorkspacePath("java/com/google/tools/build_defs.bzl"), "def function(name, deps)");
 
     BuildFile buildFile =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "load(",
             "\"//java/com/google/tools:build_defs.bzl\",",
             "\"function\"",
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/GlobFindUsagesTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/GlobFindUsagesTest.java
index f42ebe5..9e28911 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/GlobFindUsagesTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/GlobFindUsagesTest.java
@@ -23,6 +23,7 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
 import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
 import com.google.idea.blaze.base.lang.projectview.language.ProjectViewLanguage;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.psi.PsiDirectory;
 import com.intellij.psi.PsiFile;
 import com.intellij.psi.PsiManager;
@@ -40,8 +41,8 @@
 
   @Test
   public void testSimpleGlobReferencingSingleFile() {
-    PsiFile ref = createPsiFile("java/com/google/Test.java");
-    createBuildFile("java/com/google/BUILD", "glob(['**/*.java'])");
+    PsiFile ref = workspace.createPsiFile(new WorkspacePath("java/com/google/Test.java"));
+    createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(['**/*.java'])");
 
     PsiReference[] references = FindUsages.findAllReferences(ref);
     assertThat(references).hasLength(1);
@@ -50,8 +51,9 @@
 
   @Test
   public void testSimpleGlobReferencingSingleFile2() {
-    PsiFile ref = createPsiFile("java/com/google/Test.java");
-    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['*.java'])");
+    PsiFile ref = workspace.createPsiFile(new WorkspacePath("java/com/google/Test.java"));
+    BuildFile file =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(['*.java'])");
 
     GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
 
@@ -62,8 +64,9 @@
 
   @Test
   public void testSimpleGlobReferencingSingleFile3() {
-    PsiFile ref = createPsiFile("java/com/google/Test.java");
-    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['T*t.java'])");
+    PsiFile ref = workspace.createPsiFile(new WorkspacePath("java/com/google/Test.java"));
+    BuildFile file =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(['T*t.java'])");
 
     GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
 
@@ -74,9 +77,10 @@
 
   @Test
   public void testGlobReferencingMultipleFiles() {
-    PsiFile ref1 = createPsiFile("java/com/google/Test.java");
-    PsiFile ref2 = createPsiFile("java/com/google/Foo.java");
-    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['*.java'])");
+    PsiFile ref1 = workspace.createPsiFile(new WorkspacePath("java/com/google/Test.java"));
+    PsiFile ref2 = workspace.createPsiFile(new WorkspacePath("java/com/google/Foo.java"));
+    BuildFile file =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(['*.java'])");
 
     GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
 
@@ -91,8 +95,9 @@
 
   @Test
   public void testFindsSubDirectories() {
-    PsiFile ref1 = createPsiFile("java/com/google/test/Test.java");
-    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['**/*.java'])");
+    PsiFile ref1 = workspace.createPsiFile(new WorkspacePath("java/com/google/test/Test.java"));
+    BuildFile file =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(['**/*.java'])");
 
     GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
 
@@ -103,11 +108,11 @@
 
   @Test
   public void testGlobWithExcludes() {
-    PsiFile test = createPsiFile("java/com/google/tests/Test.java");
-    PsiFile foo = createPsiFile("java/com/google/Foo.java");
+    PsiFile test = workspace.createPsiFile(new WorkspacePath("java/com/google/tests/Test.java"));
+    PsiFile foo = workspace.createPsiFile(new WorkspacePath("java/com/google/Foo.java"));
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "glob(" + "  ['**/*.java']," + "  exclude = ['tests/*.java'])");
 
     GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
@@ -121,12 +126,12 @@
 
   @Test
   public void testIncludeDirectories() {
-    PsiDirectory dir = createPsiDirectory("java/com/google/tests");
-    createPsiFile("java/com/google/tests/Test.java");
-    createPsiFile("java/com/google/Foo.java");
+    PsiDirectory dir = workspace.createPsiDirectory(new WorkspacePath("java/com/google/tests"));
+    workspace.createPsiFile(new WorkspacePath("java/com/google/tests/Test.java"));
+    workspace.createPsiFile(new WorkspacePath("java/com/google/Foo.java"));
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "glob(" + "  ['**/*']," + "  exclude = ['BUILD']," + "  exclude_directories = 0)");
 
     GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
@@ -138,12 +143,13 @@
 
   @Test
   public void testExcludeDirectories() {
-    PsiDirectory dir = createPsiDirectory("java/com/google/tests");
-    createPsiFile("java/com/google/tests/Test.java");
-    createPsiFile("java/com/google/Foo.java");
+    PsiDirectory dir = workspace.createPsiDirectory(new WorkspacePath("java/com/google/tests"));
+    workspace.createPsiFile(new WorkspacePath("java/com/google/tests/Test.java"));
+    workspace.createPsiFile(new WorkspacePath("java/com/google/Foo.java"));
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD", "glob(" + "  ['**/*']," + "  exclude = ['BUILD'])");
+            new WorkspacePath("java/com/google/BUILD"),
+            "glob(" + "  ['**/*']," + "  exclude = ['BUILD'])");
 
     PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
 
@@ -153,9 +159,10 @@
 
   @Test
   public void testFilesInSubpackagesExcluded() {
-    BuildFile pkg = createBuildFile("java/com/google/BUILD", "glob(['**/*.java'])");
-    BuildFile subPkg = createBuildFile("java/com/google/other/BUILD");
-    createFile("java/com/google/other/Other.java");
+    BuildFile pkg =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(['**/*.java'])");
+    BuildFile subPkg = createBuildFile(new WorkspacePath("java/com/google/other/BUILD"));
+    workspace.createFile(new WorkspacePath("java/com/google/other/Other.java"));
 
     PsiUtils.findFirstChildOfClassRecursive(pkg, GlobExpression.class);
 
@@ -166,7 +173,7 @@
   // regression test for b/29267289
   @Test
   public void testInMemoryFileHandledGracefully() {
-    createBuildFile("java/com/google/BUILD", "glob(['**/*.java'])");
+    createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(['**/*.java'])");
 
     LightVirtualFile inMemoryFile =
         new LightVirtualFile("mockProjectViewFile", ProjectViewLanguage.INSTANCE, "");
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/LocalVariableUsagesTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/LocalVariableUsagesTest.java
index 9122f05..220c8f2 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/LocalVariableUsagesTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/findusages/LocalVariableUsagesTest.java
@@ -27,6 +27,7 @@
 import com.google.idea.blaze.base.lang.buildfile.references.LocalReference;
 import com.google.idea.blaze.base.lang.buildfile.references.TargetReference;
 import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiReference;
 import org.junit.Test;
@@ -44,7 +45,7 @@
   public void testLocalReferences() {
     BuildFile buildFile =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "localVar = 5",
             "funcall(localVar)",
             "def function(name):",
@@ -74,7 +75,8 @@
   @Test
   public void testMultipleAssignments() {
     BuildFile buildFile =
-        createBuildFile("java/com/google/BUILD", "var = 5", "var += 1", "var = 0");
+        createBuildFile(
+            new WorkspacePath("java/com/google/BUILD"), "var = 5", "var += 1", "var = 0");
 
     TargetExpression target =
         buildFile.findChildByClass(AssignmentStatement.class).getLeftHandSideExpression();
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/formatting/BuildFileFoldingBuilderTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/formatting/BuildFileFoldingBuilderTest.java
index f9ac821..6ac196a 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/formatting/BuildFileFoldingBuilderTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/formatting/BuildFileFoldingBuilderTest.java
@@ -20,6 +20,7 @@
 import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
 import com.google.idea.blaze.base.lang.buildfile.psi.LoadStatement;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.lang.folding.FoldingDescriptor;
 import com.intellij.openapi.editor.Editor;
 import org.junit.Test;
@@ -34,7 +35,7 @@
   public void testEndOfFileFunctionDelcaration() {
     // bug 28618935: test no NPE in the case where there's no
     // statement list following the func-def colon
-    BuildFile file = createBuildFile("java/com/google/BUILD", "def function():");
+    BuildFile file = createBuildFile(new WorkspacePath("java/com/google/BUILD"), "def function():");
 
     getFoldingRegions(file);
   }
@@ -43,7 +44,7 @@
   public void testFuncDefStatementsFolded() {
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "# multi-line comment, not folded",
             "# second line of comment",
             "def function(arg1, arg2):",
@@ -62,7 +63,7 @@
   public void testRulesFolded() {
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "java_library(",
             "    name = 'lib',",
             "    srcs = glob(['*.java']),",
@@ -77,7 +78,7 @@
   public void testLoadStatementFolded() {
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "load(",
             "   '//java/com/foo/build_defs.bzl',",
             "   'function1',",
@@ -91,7 +92,7 @@
   }
 
   private FoldingDescriptor[] getFoldingRegions(BuildFile file) {
-    Editor editor = openFileInEditor(file.getVirtualFile());
+    Editor editor = editorTest.openFileInEditor(file.getVirtualFile());
     return new BuildFileFoldingBuilder().buildFoldRegions(file.getNode(), editor.getDocument());
   }
 }
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/highlighting/HighlightingAnnotatorTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/highlighting/HighlightingAnnotatorTest.java
new file mode 100644
index 0000000..4a38fdf
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/highlighting/HighlightingAnnotatorTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.lang.buildfile.highlighting;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.lang.buildfile.psi.ReferenceExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.intellij.codeInsight.daemon.impl.HighlightInfo;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Integration tests for {@link HighlightingAnnotator}. */
+@RunWith(JUnit4.class)
+public class HighlightingAnnotatorTest extends BuildFileIntegrationTestCase {
+
+  @Test
+  public void testBuiltInNamesSimple() {
+    BuildFile file = createBuildFile(new WorkspacePath("BUILD"), "None");
+
+    ReferenceExpression ref = file.findChildByClass(ReferenceExpression.class);
+    assertThat(ref.getText()).isEqualTo("None");
+
+    editorTest.openFileInEditor(file.getVirtualFile());
+    List<HighlightInfo> result = testFixture.doHighlighting();
+    assertThat(result).hasSize(1);
+    assertThat(result.get(0).startOffset).isEqualTo(ref.getNode().getStartOffset());
+    assertThat(result.get(0).forcedTextAttributesKey)
+        .isEqualTo(BuildSyntaxHighlighter.BUILD_BUILTIN_NAME);
+  }
+
+  @Test
+  public void testBuiltInNames() {
+    BuildFile file =
+        createBuildFile(new WorkspacePath("BUILD"), "a = True", "b = False", "type(a)");
+
+    List<ReferenceExpression> refs =
+        PsiUtils.findAllChildrenOfClassRecursive(file, ReferenceExpression.class);
+    assertThat(refs).hasSize(4);
+    assertThat(refs.get(0).getText()).isEqualTo("True");
+    assertThat(refs.get(1).getText()).isEqualTo("False");
+    assertThat(refs.get(2).getText()).isEqualTo("type");
+
+    editorTest.openFileInEditor(file.getVirtualFile());
+    List<HighlightInfo> result = testFixture.doHighlighting();
+    assertThat(result).hasSize(3);
+    for (int i = 0; i < result.size(); i++) {
+      assertThat(result.get(i).startOffset).isEqualTo(refs.get(i).getNode().getStartOffset());
+      assertThat(result.get(i).forcedTextAttributesKey)
+          .isEqualTo(BuildSyntaxHighlighter.BUILD_BUILTIN_NAME);
+    }
+  }
+}
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/language/BuildFileTypeTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/language/BuildFileTypeTest.java
index 32825ed..2b29d6f 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/language/BuildFileTypeTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/language/BuildFileTypeTest.java
@@ -19,6 +19,7 @@
 
 import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.psi.PsiFile;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -30,13 +31,13 @@
 
   @Test
   public void testSkylarkExtensionRecognized() {
-    PsiFile file = createPsiFile("java/com/google/foo/build_defs.bzl");
+    PsiFile file = workspace.createPsiFile(new WorkspacePath("java/com/google/foo/build_defs.bzl"));
     assertThat(file).isInstanceOf(BuildFile.class);
   }
 
   @Test
   public void testExactNameMatch() {
-    PsiFile file = createPsiFile("java/com/google/foo/BUILD");
+    PsiFile file = workspace.createPsiFile(new WorkspacePath("java/com/google/foo/BUILD"));
     assertThat(file).isInstanceOf(BuildFile.class);
   }
 
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/parser/BuildParserTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/parser/BuildParserTest.java
index 1e09bc6..4d1a295 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/parser/BuildParserTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/parser/BuildParserTest.java
@@ -479,7 +479,19 @@
 
     LoadStatement stmt = stmts.get(0);
     assertThat(stmt.getImportedPath()).isEqualTo("file");
-    assertThat(stmt.getImportedSymbolNames()).isEqualTo(new String[] {"foo", "bar"});
+    assertThat(stmt.getVisibleSymbolNames()).isEqualTo(new String[] {"foo", "bar"});
+    assertNoErrors();
+  }
+
+  @Test
+  public void testLoadWithAlias() throws Exception {
+    ASTNode tree = createAST("load('file', 'foo', baz = 'bar',)\n");
+    List<LoadStatement> stmts = getTopLevelNodesOfType(tree, LoadStatement.class);
+    assertThat(stmts).hasSize(1);
+
+    LoadStatement stmt = stmts.get(0);
+    assertThat(stmt.getImportedPath()).isEqualTo("file");
+    assertThat(stmt.getVisibleSymbolNames()).isEqualTo(new String[] {"foo", "baz"});
     assertNoErrors();
   }
 
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/refactor/FileCopyTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/refactor/FileCopyTest.java
index be78197..5f18dd5 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/refactor/FileCopyTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/refactor/FileCopyTest.java
@@ -16,6 +16,7 @@
 package com.google.idea.blaze.base.lang.buildfile.refactor;
 
 import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.openapi.command.WriteCommandAction;
 import com.intellij.psi.PsiDirectory;
 import com.intellij.psi.PsiElement;
@@ -31,15 +32,23 @@
 
   @Test
   public void testCopyingJavaFileReferencedByGlob() {
-    createDirectory("java");
-    PsiFile javaFile = createPsiFile("java/Test.java", "package java;", "public class Test {}");
+    workspace.createDirectory(new WorkspacePath("java"));
+    PsiFile javaFile =
+        workspace.createPsiFile(
+            new WorkspacePath("java/Test.java"), "package java;", "public class Test {}");
 
-    PsiFile javaFile2 = createPsiFile("java/Test2.java", "package java;", "public class Test2 {}");
+    PsiFile javaFile2 =
+        workspace.createPsiFile(
+            new WorkspacePath("java/Test2.java"), "package java;", "public class Test2 {}");
 
     createBuildFile(
-        "java/BUILD", "java_library(", "    name = 'lib',", "    srcs = glob(['**/*.java']),", ")");
+        new WorkspacePath("java/BUILD"),
+        "java_library(",
+        "    name = 'lib',",
+        "    srcs = glob(['**/*.java']),",
+        ")");
 
-    PsiDirectory otherDir = createPsiDirectory("java/other");
+    PsiDirectory otherDir = workspace.createPsiDirectory(new WorkspacePath("java/other"));
 
     WriteCommandAction.runWriteCommandAction(
         null,
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/refactor/RenameRefactoringTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/refactor/RenameRefactoringTest.java
index 5c057f7..71f8962 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/refactor/RenameRefactoringTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/refactor/RenameRefactoringTest.java
@@ -25,9 +25,19 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
 import com.google.idea.blaze.base.lang.buildfile.psi.TargetExpression;
 import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.PsiReference;
+import com.intellij.refactoring.move.moveClassesOrPackages.MoveDirectoryWithClassesProcessor;
 import com.intellij.refactoring.rename.RenameDialog;
 import com.intellij.refactoring.rename.RenamePsiElementProcessor;
+import java.io.IOException;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -42,13 +52,13 @@
   @Test
   public void testRenameJavaClass() {
     PsiFile javaFile =
-        createPsiFile(
-            "com/google/foo/JavaClass.java",
+        workspace.createPsiFile(
+            new WorkspacePath("com/google/foo/JavaClass.java"),
             "package com.google.foo;",
             "public class JavaClass {}");
 
     createBuildFile(
-        "com/google/foo/BUILD",
+        new WorkspacePath("com/google/foo/BUILD"),
         "java_library(name = \"ref1\", srcs = [\"//com/google/foo:JavaClass.java\"])",
         "java_library(name = \"ref2\", srcs = [\"JavaClass.java\"])",
         "java_library(name = \"ref3\", srcs = [\":JavaClass.java\"])");
@@ -61,7 +71,7 @@
 
     assertThat(references).hasSize(3);
 
-    renamePsiElement(javaFile, "NewName.java");
+    testFixture.renameElement(javaFile, "NewName.java");
 
     Set<String> newStrings =
         references.stream().map(StringLiteral::getStringContents).collect(Collectors.toSet());
@@ -79,19 +89,19 @@
   public void testRenameRule() {
     BuildFile fooPackage =
         createBuildFile(
-            "com/google/foo/BUILD",
+            new WorkspacePath("com/google/foo/BUILD"),
             "rule_type(name = \"target\")",
             "java_library(name = \"local_ref\", srcs = [\":target\"])");
 
     BuildFile barPackage =
         createBuildFile(
-            "com/google/test/bar/BUILD",
+            new WorkspacePath("com/google/test/bar/BUILD"),
             "rule_type(name = \"ref\", arg = \"//com/google/foo:target\")",
             "top_level_ref = \"//com/google/foo:target\"");
 
     FuncallExpression targetRule =
         PsiUtils.findFirstChildOfClassRecursive(fooPackage, FuncallExpression.class);
-    renamePsiElement(targetRule, "newTargetName");
+    testFixture.renameElement(targetRule, "newTargetName");
 
     assertFileContents(
         fooPackage,
@@ -107,18 +117,19 @@
   @Test
   public void testRenameSkylarkExtension() {
     BuildFile extFile =
-        createBuildFile("java/com/google/tools/build_defs.bzl", "def function(name, deps)");
+        createBuildFile(
+            new WorkspacePath("java/com/google/tools/build_defs.bzl"), "def function(name, deps)");
 
     BuildFile buildFile =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "load(",
             "\"//java/com/google:tools/build_defs.bzl\",",
             "\"function\"",
             ")",
             "function(name = \"name\", deps = []");
 
-    renamePsiElement(extFile, "skylark.bzl");
+    testFixture.renameElement(extFile, "skylark.bzl");
 
     assertFileContents(
         buildFile,
@@ -132,11 +143,12 @@
   @Test
   public void testRenameLoadedFunction() {
     BuildFile extFile =
-        createBuildFile("java/com/google/tools/build_defs.bzl", "def function(name, deps)");
+        createBuildFile(
+            new WorkspacePath("java/com/google/tools/build_defs.bzl"), "def function(name, deps)");
 
     BuildFile buildFile =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "load(",
             "\"//java/com/google/tools:build_defs.bzl\",",
             "\"function\"",
@@ -144,7 +156,7 @@
             "function(name = \"name\", deps = []");
 
     FunctionStatement fn = extFile.findChildByClass(FunctionStatement.class);
-    renamePsiElement(fn, "action");
+    testFixture.renameElement(fn, "action");
 
     assertFileContents(extFile, "def action(name, deps)");
 
@@ -159,12 +171,12 @@
 
   @Test
   public void testRenameLocalVariable() {
-    BuildFile file = createBuildFile("java/com/google/BUILD", "a = 1", "c = a");
+    BuildFile file = createBuildFile(new WorkspacePath("java/com/google/BUILD"), "a = 1", "c = a");
 
     TargetExpression target = PsiUtils.findFirstChildOfClassRecursive(file, TargetExpression.class);
     assertThat(target.getText()).isEqualTo("a");
 
-    renamePsiElement(target, "b");
+    testFixture.renameElement(target, "b");
 
     assertFileContents(file, "b = 1", "c = b");
   }
@@ -172,18 +184,18 @@
   // all references, including path fragments in labels, should be renamed.
   @Test
   public void testRenameDirectory() {
-    createBuildFile("java/com/baz/BUILD");
-    createBuildFile("java/com/google/tools/BUILD");
+    createBuildFile(new WorkspacePath("java/com/baz/BUILD"));
+    createBuildFile(new WorkspacePath("java/com/google/tools/BUILD"));
     BuildFile buildFile =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "load(",
             "\"//java/com/google/tools:build_defs.bzl\",",
             "\"function\"",
             ")",
             "function(name = \"name\", deps = [\"//java/com/baz:target\"]");
 
-    renameDirectory("java/com", "java/alt");
+    renameDirectory(new WorkspacePath("java/com"), new WorkspacePath("java/alt"));
 
     assertFileContents(
         buildFile,
@@ -197,11 +209,12 @@
   @Test
   public void testRenameFunctionParameter() {
     BuildFile extFile =
-        createBuildFile("java/com/google/tools/build_defs.bzl", "def function(name, deps)");
+        createBuildFile(
+            new WorkspacePath("java/com/google/tools/build_defs.bzl"), "def function(name, deps)");
 
     BuildFile buildFile =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "load(",
             "\"//java/com/google/tools:build_defs.bzl\",",
             "\"function\"",
@@ -210,7 +223,7 @@
 
     FunctionStatement fn = extFile.findChildByClass(FunctionStatement.class);
     Parameter param = fn.getParameterList().findParameterByName("deps");
-    renamePsiElement(param, "exports");
+    testFixture.renameElement(param, "exports");
 
     assertFileContents(extFile, "def function(name, exports)");
 
@@ -225,7 +238,7 @@
 
   @Test
   public void testRenameSuggestionForBuildFile() {
-    BuildFile buildFile = createBuildFile("java/com/google/BUILD");
+    BuildFile buildFile = createBuildFile(new WorkspacePath("java/com/google/BUILD"));
     RenamePsiElementProcessor processor = RenamePsiElementProcessor.forElement(buildFile);
     RenameDialog dialog = processor.createRenameDialog(getProject(), buildFile, buildFile, null);
     String[] suggestions = dialog.getSuggestedNames();
@@ -234,11 +247,41 @@
 
   @Test
   public void testRenameSuggestionForSkylarkFile() {
-    BuildFile buildFile = createBuildFile("java/com/google/tools/build_defs.bzl");
+    BuildFile buildFile =
+        createBuildFile(new WorkspacePath("java/com/google/tools/build_defs.bzl"));
     RenamePsiElementProcessor processor = RenamePsiElementProcessor.forElement(buildFile);
     RenameDialog dialog = processor.createRenameDialog(getProject(), buildFile, buildFile, null);
     String[] suggestions = dialog.getSuggestedNames();
     assertThat(suggestions[0]).isEqualTo("build_defs.bzl");
   }
 
+  private static <T> List<T> findAllReferencingElementsOfType(
+      PsiElement target, Class<T> referenceType) {
+    return Arrays.stream(FindUsages.findAllReferences(target))
+        .map(PsiReference::getElement)
+        .filter(referenceType::isInstance)
+        .map(e -> (T) e)
+        .collect(Collectors.toList());
+  }
+
+  private PsiDirectory renameDirectory(WorkspacePath oldPath, WorkspacePath newPath) {
+    try {
+      VirtualFile original = fileSystem.findFile(workspaceRoot.fileForPath(oldPath).getPath());
+      PsiDirectory originalPsi = PsiManager.getInstance(getProject()).findDirectory(original);
+      assertThat(originalPsi).isNotNull();
+
+      VirtualFile destination =
+          fileSystem.findOrCreateDirectory(workspaceRoot.fileForPath(newPath).getPath());
+      PsiDirectory destPsi = PsiManager.getInstance(getProject()).findDirectory(destination);
+      assertThat(destPsi).isNotNull();
+
+      new MoveDirectoryWithClassesProcessor(
+              getProject(), new PsiDirectory[] {originalPsi}, destPsi, true, true, false, null)
+          .run();
+      return destPsi;
+
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
 }
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/GlobReferenceTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/GlobReferenceTest.java
index 62a2de8..cc9f04c 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/GlobReferenceTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/GlobReferenceTest.java
@@ -21,6 +21,7 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
 import com.google.idea.blaze.base.lang.buildfile.psi.GlobExpression;
 import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
 import com.intellij.psi.ResolveResult;
@@ -38,8 +39,9 @@
 
   @Test
   public void testSimpleGlobReferencingSingleFile() {
-    PsiFile ref = createPsiFile("java/com/google/Test.java");
-    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['**/*.java'])");
+    PsiFile ref = workspace.createPsiFile(new WorkspacePath("java/com/google/Test.java"));
+    BuildFile file =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(['**/*.java'])");
 
     GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
     List<PsiElement> references = multiResolve(glob);
@@ -49,8 +51,9 @@
 
   @Test
   public void testSimpleGlobReferencingSingleFile2() {
-    PsiFile ref = createPsiFile("java/com/google/Test.java");
-    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['*.java'])");
+    PsiFile ref = workspace.createPsiFile(new WorkspacePath("java/com/google/Test.java"));
+    BuildFile file =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(['*.java'])");
 
     GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
     List<PsiElement> references = multiResolve(glob);
@@ -60,8 +63,9 @@
 
   @Test
   public void testSimpleGlobReferencingSingleFile3() {
-    PsiFile ref = createPsiFile("java/com/google/Test.java");
-    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['T*t.java'])");
+    PsiFile ref = workspace.createPsiFile(new WorkspacePath("java/com/google/Test.java"));
+    BuildFile file =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(['T*t.java'])");
 
     GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
     List<PsiElement> references = multiResolve(glob);
@@ -71,9 +75,10 @@
 
   @Test
   public void testGlobReferencingMultipleFiles() {
-    PsiFile ref1 = createPsiFile("java/com/google/Test.java");
-    PsiFile ref2 = createPsiFile("java/com/google/Foo.java");
-    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['*.java'])");
+    PsiFile ref1 = workspace.createPsiFile(new WorkspacePath("java/com/google/Test.java"));
+    PsiFile ref2 = workspace.createPsiFile(new WorkspacePath("java/com/google/Foo.java"));
+    BuildFile file =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(['*.java'])");
 
     GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
     List<PsiElement> references = multiResolve(glob);
@@ -83,9 +88,10 @@
 
   @Test
   public void testFindsSubDirectories() {
-    PsiFile ref1 = createPsiFile("java/com/google/test/Test.java");
-    PsiFile ref2 = createPsiFile("java/com/google/Foo.java");
-    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['**/*.java'])");
+    PsiFile ref1 = workspace.createPsiFile(new WorkspacePath("java/com/google/test/Test.java"));
+    PsiFile ref2 = workspace.createPsiFile(new WorkspacePath("java/com/google/Foo.java"));
+    BuildFile file =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(['**/*.java'])");
 
     GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
     List<PsiElement> references = multiResolve(glob);
@@ -95,11 +101,11 @@
 
   @Test
   public void testGlobWithExcludes() {
-    createPsiFile("java/com/google/tests/Test.java");
-    PsiFile foo = createPsiFile("java/com/google/Foo.java");
+    workspace.createPsiFile(new WorkspacePath("java/com/google/tests/Test.java"));
+    PsiFile foo = workspace.createPsiFile(new WorkspacePath("java/com/google/Foo.java"));
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "glob(" + "  ['**/*.java']," + "  exclude = ['tests/*.java'])");
 
     GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
@@ -110,12 +116,12 @@
 
   @Test
   public void testIncludeDirectories() {
-    createDirectory("java/com/google/tests");
-    PsiFile test = createPsiFile("java/com/google/tests/Test.java");
-    PsiFile foo = createPsiFile("java/com/google/Foo.java");
+    workspace.createDirectory(new WorkspacePath("java/com/google/tests"));
+    PsiFile test = workspace.createPsiFile(new WorkspacePath("java/com/google/tests/Test.java"));
+    PsiFile foo = workspace.createPsiFile(new WorkspacePath("java/com/google/Foo.java"));
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "glob(" + "  ['**/*']," + "  exclude = ['BUILD']," + "  exclude_directories = 0)");
 
     GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
@@ -126,12 +132,13 @@
 
   @Test
   public void testExcludeDirectories() {
-    createDirectory("java/com/google/tests");
-    PsiFile test = createPsiFile("java/com/google/tests/Test.java");
-    PsiFile foo = createPsiFile("java/com/google/Foo.java");
+    workspace.createDirectory(new WorkspacePath("java/com/google/tests"));
+    PsiFile test = workspace.createPsiFile(new WorkspacePath("java/com/google/tests/Test.java"));
+    PsiFile foo = workspace.createPsiFile(new WorkspacePath("java/com/google/Foo.java"));
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD", "glob(" + "  ['**/*']," + "  exclude = ['BUILD'])");
+            new WorkspacePath("java/com/google/BUILD"),
+            "glob(" + "  ['**/*']," + "  exclude = ['BUILD'])");
 
     GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(file, GlobExpression.class);
     List<PsiElement> references = multiResolve(glob);
@@ -141,9 +148,10 @@
 
   @Test
   public void testFilesInSubpackagesExcluded() {
-    BuildFile pkg = createBuildFile("java/com/google/BUILD", "glob(['**/*.java'])");
-    createBuildFile("java/com/google/other/BUILD");
-    createFile("java/com/google/other/Other.java");
+    BuildFile pkg =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(['**/*.java'])");
+    createBuildFile(new WorkspacePath("java/com/google/other/BUILD"));
+    workspace.createFile(new WorkspacePath("java/com/google/other/Other.java"));
 
     GlobExpression glob = PsiUtils.findFirstChildOfClassRecursive(pkg, GlobExpression.class);
     List<PsiElement> references = multiResolve(glob);
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/KeywordReferenceTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/KeywordReferenceTest.java
index fd19915..c16b25a 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/KeywordReferenceTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/KeywordReferenceTest.java
@@ -24,6 +24,7 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.FunctionStatement;
 import com.google.idea.blaze.base.lang.buildfile.psi.Parameter;
 import com.google.idea.blaze.base.lang.buildfile.psi.ParameterList;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -36,7 +37,7 @@
   public void testPlainKeywordReference() {
     BuildFile file =
         createBuildFile(
-            "java/com/google/build_defs.bzl",
+            new WorkspacePath("java/com/google/build_defs.bzl"),
             "def function(name, deps)",
             "function(name = \"name\", deps = [])");
 
@@ -55,7 +56,7 @@
   public void testKwargsReference() {
     BuildFile file =
         createBuildFile(
-            "java/com/google/build_defs.bzl",
+            new WorkspacePath("java/com/google/build_defs.bzl"),
             "def function(name, **kwargs)",
             "function(name = \"name\", deps = [])");
 
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LabelReferenceTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LabelReferenceTest.java
index 2e7c417..b14f4b7 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LabelReferenceTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LabelReferenceTest.java
@@ -24,6 +24,7 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
 import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
 import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
 import com.intellij.psi.PsiReference;
@@ -40,11 +41,11 @@
   public void testExternalFileReference() {
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "exports_files([\"test.txt\", \"//java/com/google:plugin.xml\"])");
 
-    PsiFile txtFile = createPsiFile("java/com/google/test.txt");
-    PsiFile xmlFile = createPsiFile("java/com/google/plugin.xml");
+    PsiFile txtFile = workspace.createPsiFile(new WorkspacePath("java/com/google/test.txt"));
+    PsiFile xmlFile = workspace.createPsiFile(new WorkspacePath("java/com/google/plugin.xml"));
 
     List<StringLiteral> strings =
         PsiUtils.findAllChildrenOfClassRecursive(file, StringLiteral.class);
@@ -57,7 +58,7 @@
   public void testLocalRuleReference() {
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "java_library(name = \"lib\")",
             "java_library(name = \"foo\", deps = [\":lib\"])",
             "java_library(name = \"bar\", deps = [\"//java/com/google:lib\"])");
@@ -81,11 +82,12 @@
 
   @Test
   public void testTargetInAnotherPackageResolves() {
-    BuildFile targetFile = createBuildFile("java/com/google/foo/BUILD", "rule(name = \"target\")");
+    BuildFile targetFile =
+        createBuildFile(new WorkspacePath("java/com/google/foo/BUILD"), "rule(name = \"target\")");
 
     BuildFile referencingFile =
         createBuildFile(
-            "java/com/google/bar/BUILD",
+            new WorkspacePath("java/com/google/bar/BUILD"),
             "rule(name = \"other\", dep = \"//java/com/google/foo:target\")");
 
     FuncallExpression target = targetFile.findRule("target");
@@ -99,29 +101,33 @@
   @Test
   public void testRuleNameDoesntCrossPackageBoundaries() {
     BuildFile targetFile =
-        createBuildFile("java/com/google/pkg/subpkg/BUILD", "rule(name = \"target\")");
+        createBuildFile(
+            new WorkspacePath("java/com/google/pkg/subpkg/BUILD"), "rule(name = \"target\")");
 
     BuildFile referencingFile =
         createBuildFile(
-            "java/com/google/pkg/BUILD", "rule(name = \"other\", dep = \":subpkg/target\")");
+            new WorkspacePath("java/com/google/pkg/BUILD"),
+            "rule(name = \"other\", dep = \":subpkg/target\")");
 
     Argument.Keyword depArgument = referencingFile.findRule("other").getKeywordArgument("dep");
 
     LabelReference ref = (LabelReference) depArgument.getValue().getReference();
     assertThat(ref.resolve()).isNull();
 
-    replaceStringContents(ref.getElement(), "//java/com/google/pkg/subpkg:target");
+    editorTest.replaceStringContents(ref.getElement(), "//java/com/google/pkg/subpkg:target");
     assertThat(ref.resolve()).isNotNull();
     assertThat(ref.resolve()).isEqualTo(targetFile.findRule("target"));
   }
 
   @Test
   public void testLabelWithImplicitRuleName() {
-    BuildFile targetFile = createBuildFile("java/com/google/foo/BUILD", "rule(name = \"foo\")");
+    BuildFile targetFile =
+        createBuildFile(new WorkspacePath("java/com/google/foo/BUILD"), "rule(name = \"foo\")");
 
     BuildFile referencingFile =
         createBuildFile(
-            "java/com/google/bar/BUILD", "rule(name = \"other\", dep = \"//java/com/google/foo\")");
+            new WorkspacePath("java/com/google/bar/BUILD"),
+            "rule(name = \"other\", dep = \"//java/com/google/foo\")");
 
     FuncallExpression target = targetFile.findRule("foo");
     assertThat(target).isNotNull();
@@ -133,10 +139,13 @@
 
   @Test
   public void testAbsoluteLabelInSkylarkExtension() {
-    BuildFile targetFile = createBuildFile("java/com/google/foo/BUILD", "rule(name = \"foo\")");
+    BuildFile targetFile =
+        createBuildFile(new WorkspacePath("java/com/google/foo/BUILD"), "rule(name = \"foo\")");
 
     BuildFile referencingFile =
-        createBuildFile("java/com/google/foo/skylark.bzl", "LIST = ['//java/com/google/foo:foo']");
+        createBuildFile(
+            new WorkspacePath("java/com/google/foo/skylark.bzl"),
+            "LIST = ['//java/com/google/foo:foo']");
 
     FuncallExpression target = targetFile.findRule("foo");
     assertThat(target).isNotNull();
@@ -148,13 +157,14 @@
 
   @Test
   public void testRulePreferredOverFile() {
-    BuildFile targetFile = createBuildFile("java/com/foo/BUILD", "java_library(name = 'lib')");
+    BuildFile targetFile =
+        createBuildFile(new WorkspacePath("java/com/foo/BUILD"), "java_library(name = 'lib')");
 
-    createDirectory("java/com/foo/lib");
+    workspace.createDirectory(new WorkspacePath("java/com/foo/lib"));
 
     BuildFile referencingFile =
         createBuildFile(
-            "java/com/google/bar/BUILD",
+            new WorkspacePath("java/com/google/bar/BUILD"),
             "java_library(",
             "    name = 'bar',",
             "    src = glob(['**/*.java'])," + "    deps = ['//java/com/foo:lib'],",
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LoadedSkylarkExtensionTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LoadedSkylarkExtensionTest.java
index c30e57d..b43f778 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LoadedSkylarkExtensionTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LoadedSkylarkExtensionTest.java
@@ -22,6 +22,9 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
 import com.google.idea.blaze.base.lang.buildfile.psi.FunctionStatement;
 import com.google.idea.blaze.base.lang.buildfile.psi.LoadStatement;
+import com.google.idea.blaze.base.lang.buildfile.psi.LoadedSymbol;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -33,11 +36,12 @@
   @Test
   public void testStandardLoadReference() {
     BuildFile extFile =
-        createBuildFile("java/com/google/build_defs.bzl", "def function(name, deps)");
+        createBuildFile(
+            new WorkspacePath("java/com/google/build_defs.bzl"), "def function(name, deps)");
 
     BuildFile buildFile =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "load(",
             "\"//java/com/google:build_defs.bzl\",",
             "\"function\"",
@@ -50,7 +54,7 @@
     assertThat(function).isNotNull();
 
     assertThat(load.getImportedSymbolElements()).hasLength(1);
-    assertThat(load.getImportedSymbolElements()[0].getReferencedElement()).isEqualTo(function);
+    assertThat(load.getImportedSymbolElements()[0].getLoadedElement()).isEqualTo(function);
   }
 
   // TODO: If we want to support this deprecated format,
@@ -74,11 +78,16 @@
   @Test
   public void testPackageLocalImportLabelFormat() {
     BuildFile extFile =
-        createBuildFile("java/com/google/tools/build_defs.bzl", "def function(name, deps)");
+        createBuildFile(
+            new WorkspacePath("java/com/google/tools/build_defs.bzl"), "def function(name, deps)");
 
     BuildFile buildFile =
         createBuildFile(
-            "java/com/google/tools/BUILD", "load(", "\":build_defs.bzl\",", "\"function\"", ")");
+            new WorkspacePath("java/com/google/tools/BUILD"),
+            "load(",
+            "\":build_defs.bzl\",",
+            "\"function\"",
+            ")");
 
     LoadStatement load = buildFile.firstChildOfClass(LoadStatement.class);
     assertThat(load.getImportPsiElement().getReferencedElement()).isEqualTo(extFile);
@@ -88,11 +97,13 @@
   public void testMultipleImportedFunctions() {
     BuildFile extFile =
         createBuildFile(
-            "java/com/google/build_defs.bzl", "def fn1(name, deps)", "def fn2(name, deps)");
+            new WorkspacePath("java/com/google/build_defs.bzl"),
+            "def fn1(name, deps)",
+            "def fn2(name, deps)");
 
     BuildFile buildFile =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "load(",
             "\"//java/com/google:build_defs.bzl\",",
             "\"fn1\"",
@@ -110,11 +121,12 @@
   @Test
   public void testFuncallReference() {
     BuildFile extFile =
-        createBuildFile("java/com/google/tools/build_defs.bzl", "def function(name, deps)");
+        createBuildFile(
+            new WorkspacePath("java/com/google/tools/build_defs.bzl"), "def function(name, deps)");
 
     BuildFile buildFile =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "load(",
             "\"//java/com/google/tools:build_defs.bzl\",",
             "\"function\"",
@@ -128,17 +140,36 @@
     assertThat(funcall.getReferencedElement()).isEqualTo(function);
   }
 
+  @Test
+  public void testAliasedFuncallReference() {
+    createBuildFile(
+        new WorkspacePath("java/com/google/tools/build_defs.bzl"), "def function(name, deps)");
+
+    BuildFile buildFile =
+        createBuildFile(
+            new WorkspacePath("java/com/google/BUILD"),
+            "load('//java/com/google/tools:build_defs.bzl', newName = 'function'),",
+            "newName(name = \"name\", deps = []");
+
+    LoadedSymbol loadedSymbol =
+        PsiUtils.findFirstChildOfClassRecursive(buildFile, LoadedSymbol.class);
+    FuncallExpression funcall = buildFile.firstChildOfClass(FuncallExpression.class);
+
+    assertThat(funcall.getReferencedElement()).isEqualTo(loadedSymbol.getVisibleElement());
+  }
+
   // relative paths in skylark extensions which lie in subdirectories
   // are relative to the parent blaze package directory
   @Test
   public void testRelativePathInSubdirectory() {
-    createFile("java/com/google/BUILD");
+    workspace.createFile(new WorkspacePath("java/com/google/BUILD"));
     BuildFile referencedFile =
         createBuildFile(
-            "java/com/google/nonPackageSubdirectory/skylark.bzl", "def function(): return");
+            new WorkspacePath("java/com/google/nonPackageSubdirectory/skylark.bzl"),
+            "def function(): return");
     BuildFile file =
         createBuildFile(
-            "java/com/google/nonPackageSubdirectory/other.bzl",
+            new WorkspacePath("java/com/google/nonPackageSubdirectory/other.bzl"),
             "load(" + "    ':nonPackageSubdirectory/skylark.bzl',",
             "    'function',",
             ")",
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LocalReferenceTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LocalReferenceTest.java
index efa232d..b058dcc 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LocalReferenceTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/LocalReferenceTest.java
@@ -21,8 +21,12 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.AssignmentStatement;
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
 import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.FunctionStatement;
+import com.google.idea.blaze.base.lang.buildfile.psi.Parameter;
 import com.google.idea.blaze.base.lang.buildfile.psi.ReferenceExpression;
 import com.google.idea.blaze.base.lang.buildfile.psi.TargetExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.psi.PsiElement;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -36,7 +40,7 @@
 
   @Test
   public void testCreatesReference() {
-    BuildFile file = createBuildFile("java/com/google/BUILD", "a = 1", "c = a");
+    BuildFile file = createBuildFile(new WorkspacePath("java/com/google/BUILD"), "a = 1", "c = a");
 
     AssignmentStatement[] stmts = file.childrenOfClass(AssignmentStatement.class);
     assertThat(stmts).hasLength(2);
@@ -48,7 +52,7 @@
 
   @Test
   public void testReferenceResolves() {
-    BuildFile file = createBuildFile("java/com/google/BUILD", "a = 1", "c = a");
+    BuildFile file = createBuildFile(new WorkspacePath("java/com/google/BUILD"), "a = 1", "c = a");
 
     AssignmentStatement[] stmts = file.childrenOfClass(AssignmentStatement.class);
     ReferenceExpression ref = (ReferenceExpression) stmts[1].getAssignedValue();
@@ -59,7 +63,8 @@
 
   @Test
   public void testTargetInOuterScope() {
-    BuildFile file = createBuildFile("java/com/google/BUILD", "a = 1", "function(c = a)");
+    BuildFile file =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "a = 1", "function(c = a)");
 
     TargetExpression target =
         file.findChildByClass(AssignmentStatement.class).getLeftHandSideExpression();
@@ -71,7 +76,8 @@
 
   @Test
   public void testReferenceInsideFuncallExpression() {
-    BuildFile file = createBuildFile("java/com/google/BUILD", "a = 1", "a.function(c)");
+    BuildFile file =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "a = 1", "a.function(c)");
 
     TargetExpression target =
         file.findChildByClass(AssignmentStatement.class).getLeftHandSideExpression();
@@ -79,4 +85,19 @@
     ReferenceExpression ref = funcall.firstChildOfClass(ReferenceExpression.class);
     assertThat(ref.getReferencedElement()).isEqualTo(target);
   }
+
+  @Test
+  public void testReferenceToFunctionArg() {
+    BuildFile file =
+        createBuildFile(
+            new WorkspacePath("java/com/google/defs.bzl"),
+            "def function(arg1, arg2):",
+            "  arg1(arg2)");
+
+    FunctionStatement def = file.findFunctionInScope("function");
+    FuncallExpression call = PsiUtils.findFirstChildOfClassRecursive(file, FuncallExpression.class);
+    Parameter fnParam = def.getParameterList().findParameterByName("arg1");
+    assertThat(fnParam).isNotNull();
+    assertThat(call.getReference().resolve()).isEqualTo(fnParam);
+  }
 }
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/PackageReferenceTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/PackageReferenceTest.java
index 389935f..afb3760 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/PackageReferenceTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/references/PackageReferenceTest.java
@@ -23,6 +23,7 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
 import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
 import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.psi.PsiReference;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -34,11 +35,12 @@
 
   @Test
   public void testDirectReferenceResolves() {
-    BuildFile buildFile1 = createBuildFile("java/com/google/tools/BUILD", "# contents");
+    BuildFile buildFile1 =
+        createBuildFile(new WorkspacePath("java/com/google/tools/BUILD"), "# contents");
 
     BuildFile buildFile2 =
         createBuildFile(
-            "java/com/google/other/BUILD",
+            new WorkspacePath("java/com/google/other/BUILD"),
             "package_group(name = \"grp\", packages = [\"//java/com/google/tools\"])");
 
     Argument.Keyword packagesArg =
@@ -54,11 +56,12 @@
   @Test
   public void testLabelFragmentResolves() {
     BuildFile buildFile1 =
-        createBuildFile("java/com/google/tools/BUILD", "java_library(name = \"lib\")");
+        createBuildFile(
+            new WorkspacePath("java/com/google/tools/BUILD"), "java_library(name = \"lib\")");
 
     BuildFile buildFile2 =
         createBuildFile(
-            "java/com/google/other/BUILD",
+            new WorkspacePath("java/com/google/other/BUILD"),
             "java_library(name = \"lib2\", exports = [\"//java/com/google/tools:lib\"])");
 
     FuncallExpression libTarget = buildFile1.firstChildOfClass(FuncallExpression.class);
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/search/BlazePackageTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/search/BlazePackageTest.java
index 5ad3bd2..901ff0d 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/search/BlazePackageTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/search/BlazePackageTest.java
@@ -19,6 +19,7 @@
 
 import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.psi.PsiFile;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -30,8 +31,9 @@
 
   @Test
   public void testFindPackage() {
-    BuildFile packageFile = createBuildFile("java/com/google/BUILD");
-    PsiFile subDirFile = createPsiFile("java/com/google/tools/test.txt");
+    BuildFile packageFile = createBuildFile(new WorkspacePath("java/com/google/BUILD"));
+    PsiFile subDirFile =
+        workspace.createPsiFile(new WorkspacePath("java/com/google/tools/test.txt"));
     BlazePackage blazePackage = BlazePackage.getContainingPackage(subDirFile);
     assertThat(blazePackage).isNotNull();
     assertThat(blazePackage.buildFile).isEqualTo(packageFile);
@@ -39,8 +41,8 @@
 
   @Test
   public void testScopeDoesntCrossPackageBoundary() {
-    BuildFile pkg = createBuildFile("java/com/google/BUILD");
-    BuildFile subpkg = createBuildFile("java/com/google/other/BUILD");
+    BuildFile pkg = createBuildFile(new WorkspacePath("java/com/google/BUILD"));
+    BuildFile subpkg = createBuildFile(new WorkspacePath("java/com/google/other/BUILD"));
 
     BlazePackage blazePackage = BlazePackage.getContainingPackage(pkg);
     assertThat(blazePackage.buildFile).isEqualTo(pkg);
@@ -49,9 +51,9 @@
 
   @Test
   public void testScopeIncludesSubdirectoriesWhichAreNotBlazePackages() {
-    BuildFile pkg = createBuildFile("java/com/google/BUILD");
-    createBuildFile("java/com/google/foo/bar/BUILD");
-    PsiFile subDirFile = createPsiFile("java/com/google/foo/test.txt");
+    BuildFile pkg = createBuildFile(new WorkspacePath("java/com/google/BUILD"));
+    createBuildFile(new WorkspacePath("java/com/google/foo/bar/BUILD"));
+    PsiFile subDirFile = workspace.createPsiFile(new WorkspacePath("java/com/google/foo/test.txt"));
 
     BlazePackage blazePackage = BlazePackage.getContainingPackage(subDirFile);
     assertThat(blazePackage.buildFile).isEqualTo(pkg);
@@ -60,9 +62,9 @@
 
   @Test
   public void testScopeLimitedToBlazeFiles() {
-    BuildFile pkg = createBuildFile("java/com/google/BUILD");
-    createBuildFile("java/com/google/foo/bar/BUILD");
-    PsiFile subDirFile = createPsiFile("java/com/google/foo/test.txt");
+    BuildFile pkg = createBuildFile(new WorkspacePath("java/com/google/BUILD"));
+    createBuildFile(new WorkspacePath("java/com/google/foo/bar/BUILD"));
+    PsiFile subDirFile = workspace.createPsiFile(new WorkspacePath("java/com/google/foo/test.txt"));
 
     BlazePackage blazePackage = BlazePackage.getContainingPackage(subDirFile);
     assertThat(blazePackage.buildFile).isEqualTo(pkg);
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/search/GlobalWordIndexTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/search/GlobalWordIndexTest.java
index 4fdaf86..c15073b 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/search/GlobalWordIndexTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/search/GlobalWordIndexTest.java
@@ -16,6 +16,7 @@
 package com.google.idea.blaze.base.lang.buildfile.search;
 
 import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.impl.cache.CacheManager;
 import com.intellij.psi.search.GlobalSearchScope;
@@ -36,15 +37,16 @@
 
   @Test
   public void testWordsInComments() {
-    VirtualFile file = createFile("java/com/google/BUILD", "# words in comments");
+    VirtualFile file =
+        workspace.createFile(new WorkspacePath("java/com/google/BUILD"), "# words in comments");
     assertContainsWords(file, UsageSearchContext.IN_COMMENTS, "words", "in", "comments");
   }
 
   @Test
   public void testWordsInStrings() {
     VirtualFile file =
-        createFile(
-            "java/com/google/BUILD",
+        workspace.createFile(
+            new WorkspacePath("java/com/google/BUILD"),
             "name = \"long name with spaces\",",
             "src = [\"name_without_spaces\"]");
     assertContainsWords(
@@ -60,8 +62,8 @@
   @Test
   public void testWordsInCode() {
     VirtualFile file =
-        createFile(
-            "java/com/google/BUILD",
+        workspace.createFile(
+            new WorkspacePath("java/com/google/BUILD"),
             "java_library(",
             "name = \"long name with spaces\",",
             "src = [\"name_without_spaces\"]",
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/validation/GlobValidationTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/validation/GlobValidationTest.java
index 2715bbe..135f34f 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/validation/GlobValidationTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/buildfile/validation/GlobValidationTest.java
@@ -21,6 +21,7 @@
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
 import com.google.idea.blaze.base.lang.buildfile.psi.GlobExpression;
 import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.codeInsight.daemon.impl.AnnotationHolderImpl;
 import com.intellij.lang.annotation.Annotation;
 import com.intellij.lang.annotation.AnnotationHolder;
@@ -38,14 +39,17 @@
 
   @Test
   public void testNormalGlob() {
-    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['**/*.java'])");
+    BuildFile file =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(['**/*.java'])");
 
     assertNoErrors(file);
   }
 
   @Test
   public void testNamedIncludeArgument() {
-    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(include = ['**/*.java'])");
+    BuildFile file =
+        createBuildFile(
+            new WorkspacePath("java/com/google/BUILD"), "glob(include = ['**/*.java'])");
 
     assertNoErrors(file);
   }
@@ -54,7 +58,7 @@
   public void testAllArguments() {
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "glob(['**/*.java'], exclude = ['test/*.java'], exclude_directories = 0)");
 
     assertNoErrors(file);
@@ -62,14 +66,17 @@
 
   @Test
   public void testEmptyExcludeList() {
-    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(['**/*.java'], exclude = [])");
+    BuildFile file =
+        createBuildFile(
+            new WorkspacePath("java/com/google/BUILD"), "glob(['**/*.java'], exclude = [])");
 
     assertNoErrors(file);
   }
 
   @Test
   public void testNoIncludesError() {
-    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(exclude = ['BUILD'])");
+    BuildFile file =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(exclude = ['BUILD'])");
 
     assertHasError(file, "Glob expression must contain at least one included string");
   }
@@ -77,14 +84,16 @@
   @Test
   public void testSingletonExcludeArgumentError() {
     BuildFile file =
-        createBuildFile("java/com/google/BUILD", "glob(['**/*.java'], exclude = 'BUILD')");
+        createBuildFile(
+            new WorkspacePath("java/com/google/BUILD"), "glob(['**/*.java'], exclude = 'BUILD')");
 
     assertHasError(file, "Glob parameter 'exclude' must be a list of strings");
   }
 
   @Test
   public void testSingletonIncludeArgumentError() {
-    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(include = '**/*.java')");
+    BuildFile file =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(include = '**/*.java')");
 
     assertHasError(file, "Glob parameter 'include' must be a list of strings");
   }
@@ -93,7 +102,7 @@
   public void testInvalidExcludeDirectoriesValue() {
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "glob(['**/*.java'], exclude = ['test/*.java'], exclude_directories = true)");
 
     assertHasError(file, "exclude_directories parameter to glob must be 0 or 1");
@@ -103,14 +112,16 @@
   public void testUnrecognizedArgumentError() {
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD", "glob(['**/*.java'], exclude = ['test/*.java'], extra = 1)");
+            new WorkspacePath("java/com/google/BUILD"),
+            "glob(['**/*.java'], exclude = ['test/*.java'], extra = 1)");
 
     assertHasError(file, "Unrecognized glob argument");
   }
 
   @Test
   public void testInvalidListArgumentValue() {
-    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(include = foo)");
+    BuildFile file =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(include = foo)");
 
     assertHasError(file, "Glob parameter 'include' must be a list of strings");
   }
@@ -118,17 +129,18 @@
   @Test
   public void testLocalVariableReference() {
     BuildFile file =
-        createBuildFile("java/com/google/BUILD", "foo = ['*.java']", "glob(include = foo)");
+        createBuildFile(
+            new WorkspacePath("java/com/google/BUILD"), "foo = ['*.java']", "glob(include = foo)");
 
     assertNoErrors(file);
   }
 
   @Test
   public void testLoadedVariableReference() {
-    createBuildFile("java/com/foo/vars.bzl", "LIST_VAR = ['*']");
+    createBuildFile(new WorkspacePath("java/com/foo/vars.bzl"), "LIST_VAR = ['*']");
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "load('//java/com/foo:vars.bzl', 'LIST_VAR')",
             "glob(include = LIST_VAR)");
 
@@ -137,10 +149,11 @@
 
   @Test
   public void testInvalidLoadedVariableReference() {
-    createBuildFile("java/com/foo/vars.bzl", "LIST_VAR = ['*']", "def function()");
+    createBuildFile(
+        new WorkspacePath("java/com/foo/vars.bzl"), "LIST_VAR = ['*']", "def function()");
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "load('//java/com/foo:vars.bzl', 'LIST_VAR', 'function')",
             "glob(include = function)");
 
@@ -149,14 +162,16 @@
 
   @Test
   public void testUnresolvedReferenceExpression() {
-    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(include = ref)");
+    BuildFile file =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(include = ref)");
 
     assertHasError(file, "Glob parameter 'include' must be a list of strings");
   }
 
   @Test
   public void testPossibleListExpressionFuncallExpression() {
-    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(include = fn.list)");
+    BuildFile file =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(include = fn.list)");
 
     assertNoErrors(file);
   }
@@ -165,7 +180,9 @@
   public void testPossibleListExpressionParameter() {
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD", "def function(param1, param2):", "    glob(include = param1)");
+            new WorkspacePath("java/com/google/BUILD"),
+            "def function(param1, param2):",
+            "    glob(include = param1)");
 
     assertNoErrors(file);
   }
@@ -173,7 +190,8 @@
   @Test
   public void testNestedGlobs() {
     // blaze accepts nested globs
-    BuildFile file = createBuildFile("java/com/google/BUILD", "glob(glob(['*.java']))");
+    BuildFile file =
+        createBuildFile(new WorkspacePath("java/com/google/BUILD"), "glob(glob(['*.java']))");
 
     assertNoErrors(file);
   }
@@ -181,7 +199,10 @@
   @Test
   public void testKnownInvalidResolvedListExpression() {
     BuildFile file =
-        createBuildFile("java/com/google/BUILD", "bool_literal = True", "glob(bool_literal)");
+        createBuildFile(
+            new WorkspacePath("java/com/google/BUILD"),
+            "bool_literal = True",
+            "glob(bool_literal)");
 
     assertHasError(file, "Glob parameter 'include' must be a list of strings");
   }
@@ -189,7 +210,10 @@
   @Test
   public void testKnownInvalidResolvedString() {
     BuildFile file =
-        createBuildFile("java/com/google/BUILD", "bool_literal = True", "glob([bool_literal])");
+        createBuildFile(
+            new WorkspacePath("java/com/google/BUILD"),
+            "bool_literal = True",
+            "glob([bool_literal])");
 
     assertHasError(file, "Glob parameter 'include' must be a list of strings");
   }
@@ -197,7 +221,9 @@
   @Test
   public void testPossibleStringLiteralIfStatement() {
     BuildFile file =
-        createBuildFile("java/com/google/BUILD", "glob(include = ['*.java', if test : a else b])");
+        createBuildFile(
+            new WorkspacePath("java/com/google/BUILD"),
+            "glob(include = ['*.java', if test : a else b])");
 
     // we don't know what the IfStatement evaluates to
     assertNoErrors(file);
@@ -207,7 +233,7 @@
   public void testPossibleStringLiteralParameter() {
     BuildFile file =
         createBuildFile(
-            "java/com/google/BUILD",
+            new WorkspacePath("java/com/google/BUILD"),
             "def function(param1, param2):",
             "    glob(include = [param1])");
 
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewCompletionTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewCompletionTest.java
index 3628b85..99390e2 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewCompletionTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewCompletionTest.java
@@ -18,7 +18,9 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.common.base.Joiner;
+import com.google.idea.blaze.base.lang.projectview.completion.ProjectViewKeywordCompletionContributor;
 import com.google.idea.blaze.base.model.primitives.LanguageClass;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.google.idea.blaze.base.model.primitives.WorkspaceType;
 import com.google.idea.blaze.base.projectview.section.SectionParser;
 import com.google.idea.blaze.base.projectview.section.sections.Sections;
@@ -48,13 +50,14 @@
   @Test
   public void testSectionTypeKeywords() {
     setInput("<caret>");
-    String[] keywords = getCompletionItemsAsStrings();
+    String[] keywords = editorTest.getCompletionItemsAsStrings();
 
     assertThat(keywords)
         .asList()
         .containsAllIn(
             Sections.getUndeprecatedParsers()
                 .stream()
+                .filter(ProjectViewKeywordCompletionContributor::handledSectionType)
                 .map(SectionParser::getName)
                 .collect(Collectors.toList()));
   }
@@ -62,7 +65,7 @@
   @Test
   public void testColonAndNewLineAndIndentInsertedAfterListSection() {
     setInput("direc<caret>");
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertResult("directories:", "  <caret>");
   }
 
@@ -82,7 +85,7 @@
   @Test
   public void testColonDividerAndSpaceInsertedAfterScalarSection() {
     setInput("works<caret>");
-    assertThat(completeIfUnique()).isTrue();
+    assertThat(editorTest.completeIfUnique()).isTrue();
     assertResult("workspace_type: <caret>");
   }
 
@@ -90,7 +93,7 @@
   public void testNoKeywordCompletionInListItem() {
     setInput("directories:", "  <caret>");
 
-    String[] completionItems = getCompletionItemsAsStrings();
+    String[] completionItems = editorTest.getCompletionItemsAsStrings();
     if (completionItems == null) {
       Assert.fail("Spurious completion. New file contents: " + testFixture.getFile().getText());
     }
@@ -101,7 +104,7 @@
   public void testNoKeywordCompletionAfterKeyword() {
     setInput("import <caret>");
 
-    String[] completionItems = getCompletionItemsAsStrings();
+    String[] completionItems = editorTest.getCompletionItemsAsStrings();
     if (completionItems == null) {
       Assert.fail("Spurious completion. New file contents: " + testFixture.getFile().getText());
     }
@@ -112,7 +115,7 @@
   public void testWorkspaceTypeCompletion() {
     setInput("workspace_type: <caret>");
 
-    String[] types = getCompletionItemsAsStrings();
+    String[] types = editorTest.getCompletionItemsAsStrings();
 
     assertThat(types)
         .asList()
@@ -126,7 +129,7 @@
   public void testAdditionalLanguagesCompletion() {
     setInput("additional_languages:", "  <caret>");
 
-    String[] types = getCompletionItemsAsStrings();
+    String[] types = editorTest.getCompletionItemsAsStrings();
 
     assertThat(types)
         .asList()
@@ -140,9 +143,9 @@
   public void testUniqueDirectoryCompleted() {
     setInput("import <caret>");
 
-    createDirectory("java");
+    workspace.createDirectory(new WorkspacePath("java"));
 
-    String[] completionItems = getCompletionItemsAsStrings();
+    String[] completionItems = editorTest.getCompletionItemsAsStrings();
     assertThat(completionItems).isNull();
     assertResult("import java<caret>");
   }
@@ -151,9 +154,9 @@
   public void testUniqueMultiSegmentDirectoryCompleted() {
     setInput("import <caret>");
 
-    createDirectory("java/com/google");
+    workspace.createDirectory(new WorkspacePath("java/com/google"));
 
-    String[] completionItems = getCompletionItemsAsStrings();
+    String[] completionItems = editorTest.getCompletionItemsAsStrings();
     assertThat(completionItems).isNull();
     assertResult("import java/com/google<caret>");
   }
@@ -162,41 +165,41 @@
   public void testNonDirectoriesIgnored() {
     setInput("import <caret>");
 
-    createDirectory("java/com/google");
-    createFile("java/IgnoredFile.java");
+    workspace.createDirectory(new WorkspacePath("java/com/google"));
+    workspace.createFile(new WorkspacePath("java/IgnoredFile.java"));
 
-    String[] completionItems = getCompletionItemsAsStrings();
+    String[] completionItems = editorTest.getCompletionItemsAsStrings();
     assertThat(completionItems).isNull();
     assertResult("import java/com/google<caret>");
   }
 
   @Test
   public void testMultipleDirectoryOptions() {
-    createDirectory("foo");
-    createDirectory("bar");
-    createDirectory("other");
-    createDirectory("ostrich/foo");
-    createDirectory("ostrich/fooz");
+    workspace.createDirectory(new WorkspacePath("foo"));
+    workspace.createDirectory(new WorkspacePath("bar"));
+    workspace.createDirectory(new WorkspacePath("other"));
+    workspace.createDirectory(new WorkspacePath("ostrich/foo"));
+    workspace.createDirectory(new WorkspacePath("ostrich/fooz"));
 
     setInput("targets:", "  //o<caret>");
 
-    String[] completionItems = getCompletionItemsAsSuggestionStrings();
+    String[] completionItems = editorTest.getCompletionItemsAsSuggestionStrings();
     assertThat(completionItems).asList().containsExactly("other", "ostrich");
 
-    performTypingAction(testFixture.getEditor(), 's');
+    editorTest.performTypingAction(testFixture.getEditor(), 's');
 
-    completionItems = getCompletionItemsAsStrings();
+    completionItems = editorTest.getCompletionItemsAsStrings();
     assertThat(completionItems).isNull();
     assertResult("targets:", "  //ostrich<caret>");
   }
 
   @Test
-  public void testRuleCompletion() {
-    createFile("BUILD", "java_library(name = 'lib')");
+  public void testTargetCompletion() {
+    workspace.createFile(new WorkspacePath("BUILD"), "java_library(name = 'lib')");
 
     setInput("targets:", "  //:<caret>");
 
-    String[] completionItems = getCompletionItemsAsSuggestionStrings();
+    String[] completionItems = editorTest.getCompletionItemsAsSuggestionStrings();
     assertThat(completionItems).isNull();
     assertResult("targets:", "  //:lib<caret>");
   }
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewIntegrationTestCase.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewIntegrationTestCase.java
index 96b5f8a..361b0bb 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewIntegrationTestCase.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewIntegrationTestCase.java
@@ -18,7 +18,8 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.idea.blaze.base.BlazeIntegrationTestCase;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
+import com.google.idea.blaze.base.EditorTestHelper;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
@@ -31,10 +32,12 @@
 
 /** Project view file specific integration test base */
 public abstract class ProjectViewIntegrationTestCase extends BlazeIntegrationTestCase {
+  protected EditorTestHelper editorTest;
 
   @Before
   public final void doSetup() {
     mockBlazeProjectDataManager(getMockBlazeProjectData());
+    editorTest = new EditorTestHelper(getProject(), testFixture);
   }
 
   private BlazeProjectData getMockBlazeProjectData() {
@@ -43,14 +46,16 @@
             null,
             ImmutableList.of(workspaceRoot.directory()),
             new ExecutionRootPath("out/crosstool/bin"),
-            new ExecutionRootPath("out/crosstool/gen"));
+            new ExecutionRootPath("out/crosstool/gen"),
+            null);
     WorkspacePathResolver workspacePathResolver =
         new WorkspacePathResolverImpl(workspaceRoot, fakeRoots);
     ArtifactLocationDecoder artifactLocationDecoder =
         new ArtifactLocationDecoderImpl(fakeRoots, workspacePathResolver);
     return new BlazeProjectData(
         0,
-        new RuleMap(ImmutableMap.of()),
+        new TargetMap(ImmutableMap.of()),
+        ImmutableMap.of(),
         fakeRoots,
         new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()),
         workspacePathResolver,
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewParserIntegrationTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewParserIntegrationTest.java
index 4fee948..0044e3c 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewParserIntegrationTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/ProjectViewParserIntegrationTest.java
@@ -21,6 +21,7 @@
 import com.google.common.collect.Lists;
 import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
 import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewPsiElement;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiErrorElement;
 import com.intellij.psi.PsiFile;
@@ -93,7 +94,7 @@
   }
 
   private String parse(String... lines) {
-    PsiFile file = createPsiFile(".blazeproject", lines);
+    PsiFile file = workspace.createPsiFile(new WorkspacePath(".blazeproject"), lines);
     collectErrors(file);
     return treeToString(file);
   }
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/references/ProjectViewLabelReferenceTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/references/ProjectViewLabelReferenceTest.java
new file mode 100644
index 0000000..179bd57
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/lang/projectview/references/ProjectViewLabelReferenceTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.lang.projectview.references;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.google.idea.blaze.base.lang.projectview.ProjectViewIntegrationTestCase;
+import com.google.idea.blaze.base.lang.projectview.psi.ProjectViewPsiSectionItem;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests that paths correctly resolve in project view files */
+@RunWith(JUnit4.class)
+public class ProjectViewLabelReferenceTest extends ProjectViewIntegrationTestCase {
+
+  @Test
+  public void testFileReference() {
+    PsiFile referencedFile =
+        workspace.createPsiFile(
+            new WorkspacePath("ijwb.bazelproject"),
+            "directories:",
+            "  java/com/google/foo",
+            "targets:",
+            "  //java/com/google/foo/...");
+    PsiFile file =
+        workspace.createPsiFile(new WorkspacePath(".bazelproject"), "import ijwb.bazelproject");
+
+    ProjectViewPsiSectionItem importItem =
+        PsiUtils.findFirstChildOfClassRecursive(file, ProjectViewPsiSectionItem.class);
+    assertThat(importItem).isNotNull();
+    assertThat(importItem.getReference().resolve()).isEqualTo(referencedFile);
+  }
+
+  @Test
+  public void testDirectoryReference() {
+    PsiDirectory directory = workspace.createPsiDirectory(new WorkspacePath("foo/bar"));
+    PsiFile projectView =
+        workspace.createPsiFile(new WorkspacePath(".bazelproject"), "directories:", "  foo/bar");
+
+    ProjectViewPsiSectionItem importItem =
+        PsiUtils.findFirstChildOfClassRecursive(projectView, ProjectViewPsiSectionItem.class);
+    assertThat(importItem).isNotNull();
+    assertThat(importItem.getReference().resolve()).isEqualTo(directory);
+  }
+
+  @Test
+  public void testTargetReference() {
+    PsiFile buildFile =
+        workspace.createPsiFile(
+            new WorkspacePath("foo/bar/BUILD"), "java_library(", "    name = 'lib',", ")");
+    PsiFile projectView =
+        workspace.createPsiFile(new WorkspacePath(".bazelproject"), "targets:", "  //foo/bar:lib");
+
+    FuncallExpression target = ((BuildFile) buildFile).findRule("lib");
+
+    ProjectViewPsiSectionItem importItem =
+        PsiUtils.findFirstChildOfClassRecursive(projectView, ProjectViewPsiSectionItem.class);
+    assertThat(importItem).isNotNull();
+    assertThat(importItem.getReference().resolve()).isEqualTo(target);
+  }
+}
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationGenericHandlerIntegrationTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationGenericHandlerIntegrationTest.java
index eac7ae9..c4f5242 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationGenericHandlerIntegrationTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationGenericHandlerIntegrationTest.java
@@ -21,7 +21,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.idea.blaze.base.BlazeIntegrationTestCase;
 import com.google.idea.blaze.base.command.BlazeCommandName;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
 import com.google.idea.blaze.base.model.primitives.TargetExpression;
@@ -68,14 +68,16 @@
             null,
             ImmutableList.of(workspaceRoot.directory()),
             new ExecutionRootPath("out/crosstool/bin"),
-            new ExecutionRootPath("out/crosstool/gen"));
+            new ExecutionRootPath("out/crosstool/gen"),
+            null);
     WorkspacePathResolver workspacePathResolver =
         new WorkspacePathResolverImpl(workspaceRoot, fakeRoots);
     ArtifactLocationDecoder artifactLocationDecoder =
         new ArtifactLocationDecoderImpl(fakeRoots, workspacePathResolver);
     return new BlazeProjectData(
         0,
-        new RuleMap(ImmutableMap.of()),
+        new TargetMap(ImmutableMap.of()),
+        ImmutableMap.of(),
         fakeRoots,
         new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()),
         workspacePathResolver,
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationRunManagerImplTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationRunManagerImplTest.java
new file mode 100644
index 0000000..6395e65
--- /dev/null
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationRunManagerImplTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.idea.blaze.base.run;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.idea.blaze.base.BlazeIntegrationTestCase;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration.BlazeCommandRunConfigurationSettingsEditor;
+import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
+import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoderImpl;
+import com.google.idea.blaze.base.sync.workspace.BlazeRoots;
+import com.google.idea.blaze.base.sync.workspace.WorkingSet;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl;
+import com.intellij.execution.RunnerAndConfigurationSettings;
+import com.intellij.execution.configurations.ConfigurationType;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.execution.impl.RunManagerImpl;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.util.Disposer;
+import org.jdom.Element;
+import org.jdom.output.Format;
+import org.jdom.output.XMLOutputter;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Integration tests for {@link BlazeCommandRunConfiguration} saved by {@link
+ * com.intellij.execution.impl.RunManagerImpl}.
+ */
+@RunWith(JUnit4.class)
+public class BlazeCommandRunConfigurationRunManagerImplTest extends BlazeIntegrationTestCase {
+
+  private RunManagerImpl runManager;
+  private BlazeCommandRunConfigurationType type;
+  private BlazeCommandRunConfiguration configuration;
+
+  @Before
+  public final void doSetup() {
+    runManager = RunManagerImpl.getInstanceImpl(getProject());
+    // Without BlazeProjectData, the configuration editor is always disabled.
+    mockBlazeProjectDataManager(getMockBlazeProjectData());
+    type = BlazeCommandRunConfigurationType.getInstance();
+
+    RunnerAndConfigurationSettings runnerAndConfigurationSettings =
+        runManager.createConfiguration("Blaze Configuration", type.getFactory());
+    runManager.addConfiguration(runnerAndConfigurationSettings, false);
+    configuration =
+        (BlazeCommandRunConfiguration) runnerAndConfigurationSettings.getConfiguration();
+  }
+
+  private BlazeProjectData getMockBlazeProjectData() {
+    BlazeRoots fakeRoots =
+        new BlazeRoots(
+            null,
+            ImmutableList.of(workspaceRoot.directory()),
+            new ExecutionRootPath("out/crosstool/bin"),
+            new ExecutionRootPath("out/crosstool/gen"),
+            null);
+    WorkspacePathResolver workspacePathResolver =
+        new WorkspacePathResolverImpl(workspaceRoot, fakeRoots);
+    ArtifactLocationDecoder artifactLocationDecoder =
+        new ArtifactLocationDecoderImpl(fakeRoots, workspacePathResolver);
+    return new BlazeProjectData(
+        0,
+        new TargetMap(ImmutableMap.of()),
+        ImmutableMap.of(),
+        fakeRoots,
+        new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()),
+        workspacePathResolver,
+        artifactLocationDecoder,
+        null,
+        null,
+        null,
+        null);
+  }
+
+  @After
+  public final void doTeardown() {
+    runManager.clearAll();
+    // We don't need to do this at setup, because it is handled by RunManagerImpl's constructor.
+    // However, clearAll() clears the configuration types, so we need to reinitialize them.
+    runManager.initializeConfigurationTypes(
+        ConfigurationType.CONFIGURATION_TYPE_EP.getExtensions());
+  }
+
+  @Test
+  public void loadStateAndGetStateShouldMatch() {
+    final Label label = new Label("//package:rule");
+    configuration.setTarget(label);
+
+    final Element element = runManager.getState();
+    runManager.loadState(element);
+    final RunConfiguration[] configurations = runManager.getAllConfigurations();
+    assertThat(configurations).hasLength(1);
+    assertThat(configurations[0]).isInstanceOf(BlazeCommandRunConfiguration.class);
+    final BlazeCommandRunConfiguration readConfiguration =
+        (BlazeCommandRunConfiguration) configurations[0];
+
+    assertThat(readConfiguration.getTarget()).isEqualTo(label);
+  }
+
+  @Test
+  public void loadStateAndGetStateElementShouldMatch() {
+    final XMLOutputter xmlOutputter = new XMLOutputter(Format.getCompactFormat());
+    configuration.setTarget(new Label("//package:rule"));
+
+    final Element initialElement = runManager.getState();
+    runManager.loadState(initialElement);
+    final Element newElement = runManager.getState();
+
+    assertThat(xmlOutputter.outputString(newElement))
+        .isEqualTo(xmlOutputter.outputString(initialElement));
+  }
+
+  @Test
+  public void loadStateAndGetStateElementShouldMatchAfterChangeAndRevert() {
+    final XMLOutputter xmlOutputter = new XMLOutputter(Format.getCompactFormat());
+    final Label label = new Label("//package:rule");
+    configuration.setTarget(label);
+
+    final Element initialElement = runManager.getState();
+    runManager.loadState(initialElement);
+    final BlazeCommandRunConfiguration modifiedConfiguration =
+        (BlazeCommandRunConfiguration) runManager.getAllConfigurations()[0];
+    modifiedConfiguration.setTarget(new Label("//new:label"));
+
+    final Element modifiedElement = runManager.getState();
+    assertThat(xmlOutputter.outputString(modifiedElement))
+        .isNotEqualTo(xmlOutputter.outputString(initialElement));
+    runManager.loadState(modifiedElement);
+    final BlazeCommandRunConfiguration revertedConfiguration =
+        (BlazeCommandRunConfiguration) runManager.getAllConfigurations()[0];
+    revertedConfiguration.setTarget(label);
+
+    final Element revertedElement = runManager.getState();
+    assertThat(xmlOutputter.outputString(revertedElement))
+        .isEqualTo(xmlOutputter.outputString(initialElement));
+  }
+
+  @Test
+  public void getStateElementShouldMatchAfterEditorApplyToAndResetFrom()
+      throws ConfigurationException {
+    final XMLOutputter xmlOutputter = new XMLOutputter(Format.getCompactFormat());
+    final BlazeCommandRunConfigurationSettingsEditor editor =
+        new BlazeCommandRunConfigurationSettingsEditor(configuration);
+    configuration.setTarget(new Label("//package:rule"));
+
+    final Element initialElement = runManager.getState();
+    editor.resetFrom(configuration);
+    editor.applyEditorTo(configuration);
+    final Element newElement = runManager.getState();
+
+    assertThat(xmlOutputter.outputString(newElement))
+        .isEqualTo(xmlOutputter.outputString(initialElement));
+
+    Disposer.dispose(editor);
+  }
+}
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationSettingsEditorTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationSettingsEditorTest.java
index 56ffef1..336930a 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationSettingsEditorTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationSettingsEditorTest.java
@@ -20,7 +20,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.idea.blaze.base.BlazeIntegrationTestCase;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
 import com.google.idea.blaze.base.model.primitives.Label;
@@ -59,14 +59,16 @@
             null,
             ImmutableList.of(workspaceRoot.directory()),
             new ExecutionRootPath("out/crosstool/bin"),
-            new ExecutionRootPath("out/crosstool/gen"));
+            new ExecutionRootPath("out/crosstool/gen"),
+            null);
     WorkspacePathResolver workspacePathResolver =
         new WorkspacePathResolverImpl(workspaceRoot, fakeRoots);
     ArtifactLocationDecoder artifactLocationDecoder =
         new ArtifactLocationDecoderImpl(fakeRoots, workspacePathResolver);
     return new BlazeProjectData(
         0,
-        new RuleMap(ImmutableMap.of()),
+        new TargetMap(ImmutableMap.of()),
+        ImmutableMap.of(),
         fakeRoots,
         new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()),
         workspacePathResolver,
diff --git a/base/tests/integrationtests/com/google/idea/blaze/base/run/TestRuleHeuristicTest.java b/base/tests/integrationtests/com/google/idea/blaze/base/run/TestTargetHeuristicTest.java
similarity index 68%
rename from base/tests/integrationtests/com/google/idea/blaze/base/run/TestRuleHeuristicTest.java
rename to base/tests/integrationtests/com/google/idea/blaze/base/run/TestTargetHeuristicTest.java
index aafa355..6cd2d7c 100644
--- a/base/tests/integrationtests/com/google/idea/blaze/base/run/TestRuleHeuristicTest.java
+++ b/base/tests/integrationtests/com/google/idea/blaze/base/run/TestTargetHeuristicTest.java
@@ -19,7 +19,7 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.idea.blaze.base.BlazeIntegrationTestCase;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.ideinfo.TestIdeInfo;
 import com.google.idea.blaze.base.ideinfo.TestIdeInfo.TestSize;
 import com.google.idea.blaze.base.model.primitives.Label;
@@ -29,75 +29,78 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-/** Integration tests for {@link TestRuleHeuristic}. */
+/** Integration tests for {@link TestTargetHeuristic}. */
 @RunWith(JUnit4.class)
-public class TestRuleHeuristicTest extends BlazeIntegrationTestCase {
+public class TestTargetHeuristicTest extends BlazeIntegrationTestCase {
 
   @Test
   public void testTestSizeMatched() throws Exception {
     File source = new File("java/com/foo/FooTest.java");
-    Collection<RuleIdeInfo> rules =
+    Collection<TargetIdeInfo> targets =
         ImmutableList.of(
-            RuleIdeInfo.builder()
+            TargetIdeInfo.builder()
                 .setLabel("//foo:test1")
                 .setKind("java_test")
                 .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.MEDIUM))
                 .build(),
-            RuleIdeInfo.builder()
+            TargetIdeInfo.builder()
                 .setLabel("//foo:test2")
                 .setKind("java_test")
                 .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.SMALL))
                 .build());
-    Label match = TestRuleHeuristic.chooseTestTargetForSourceFile(source, rules, TestSize.SMALL);
+    Label match =
+        TestTargetHeuristic.chooseTestTargetForSourceFile(source, targets, TestSize.SMALL);
     assertThat(match).isEqualTo(new Label("//foo:test2"));
   }
 
   @Test
-  public void testRuleNameMatched() throws Exception {
+  public void testTargetNameMatched() throws Exception {
     File source = new File("java/com/foo/FooTest.java");
-    Collection<RuleIdeInfo> rules =
+    Collection<TargetIdeInfo> targets =
         ImmutableList.of(
-            RuleIdeInfo.builder().setLabel("//foo:FirstTest").setKind("java_test").build(),
-            RuleIdeInfo.builder().setLabel("//foo:FooTest").setKind("java_test").build());
-    Label match = TestRuleHeuristic.chooseTestTargetForSourceFile(source, rules, null);
+            TargetIdeInfo.builder().setLabel("//foo:FirstTest").setKind("java_test").build(),
+            TargetIdeInfo.builder().setLabel("//foo:FooTest").setKind("java_test").build());
+    Label match = TestTargetHeuristic.chooseTestTargetForSourceFile(source, targets, null);
     assertThat(match).isEqualTo(new Label("//foo:FooTest"));
   }
 
   @Test
-  public void testNoMatchFallBackToFirstRule() throws Exception {
+  public void testNoMatchFallBackToFirstTarget() throws Exception {
     File source = new File("java/com/foo/FooTest.java");
-    ImmutableList<RuleIdeInfo> rules =
+    ImmutableList<TargetIdeInfo> targets =
         ImmutableList.of(
-            RuleIdeInfo.builder()
+            TargetIdeInfo.builder()
                 .setLabel("//bar:BarTest")
                 .setKind("java_test")
                 .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.MEDIUM))
                 .build(),
-            RuleIdeInfo.builder()
+            TargetIdeInfo.builder()
                 .setLabel("//foo:OtherTest")
                 .setKind("java_test")
                 .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.SMALL))
                 .build());
-    Label match = TestRuleHeuristic.chooseTestTargetForSourceFile(source, rules, TestSize.LARGE);
+    Label match =
+        TestTargetHeuristic.chooseTestTargetForSourceFile(source, targets, TestSize.LARGE);
     assertThat(match).isEqualTo(new Label("//bar:BarTest"));
   }
 
   @Test
-  public void testRuleNameCheckedBeforeTestSize() throws Exception {
+  public void testTargetNameCheckedBeforeTestSize() throws Exception {
     File source = new File("java/com/foo/FooTest.java");
-    ImmutableList<RuleIdeInfo> rules =
+    ImmutableList<TargetIdeInfo> targets =
         ImmutableList.of(
-            RuleIdeInfo.builder()
+            TargetIdeInfo.builder()
                 .setLabel("//bar:BarTest")
                 .setKind("java_test")
                 .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.SMALL))
                 .build(),
-            RuleIdeInfo.builder()
+            TargetIdeInfo.builder()
                 .setLabel("//foo:FooTest")
                 .setKind("java_test")
                 .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.MEDIUM))
                 .build());
-    Label match = TestRuleHeuristic.chooseTestTargetForSourceFile(source, rules, TestSize.SMALL);
+    Label match =
+        TestTargetHeuristic.chooseTestTargetForSourceFile(source, targets, TestSize.SMALL);
     assertThat(match).isEqualTo(new Label("//foo:FooTest"));
   }
 }
diff --git a/base/tests/unittests/com/google/idea/blaze/base/io/MockWorkspaceScanner.java b/base/tests/unittests/com/google/idea/blaze/base/io/MockWorkspaceScanner.java
deleted file mode 100644
index 2e3dea8..0000000
--- a/base/tests/unittests/com/google/idea/blaze/base/io/MockWorkspaceScanner.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2016 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.idea.blaze.base.io;
-
-import com.google.common.collect.Sets;
-import com.google.idea.blaze.base.model.primitives.WorkspacePath;
-import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
-import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
-import com.google.idea.blaze.base.sync.projectview.ImportRoots;
-import java.util.Set;
-import org.jetbrains.annotations.NotNull;
-
-/** Mocks the file system. */
-public final class MockWorkspaceScanner implements WorkspaceScanner {
-
-  Set<WorkspacePath> files = Sets.newHashSet();
-  Set<WorkspacePath> directories = Sets.newHashSet();
-
-  public MockWorkspaceScanner addFile(@NotNull WorkspacePath file) {
-    files.add(file);
-    return this;
-  }
-
-  public MockWorkspaceScanner addDirectory(@NotNull WorkspacePath file) {
-    addFile(file);
-    directories.add(file);
-    return this;
-  }
-
-  public MockWorkspaceScanner addPackage(@NotNull WorkspacePath file) {
-    addFile(new WorkspacePath(file + "/BUILD"));
-    addDirectory(file);
-    return this;
-  }
-
-  public MockWorkspaceScanner addPackages(@NotNull Iterable<WorkspacePath> files) {
-    for (WorkspacePath workspacePath : files) {
-      addPackage(workspacePath);
-    }
-    return this;
-  }
-
-  public MockWorkspaceScanner addImportRoots(@NotNull ImportRoots importRoots) {
-    addPackages(importRoots.rootDirectories());
-    addPackages(importRoots.excludeDirectories());
-    return this;
-  }
-
-  public MockWorkspaceScanner addProjectView(
-      WorkspaceRoot workspaceRoot, ProjectViewSet projectViewSet) {
-    ImportRoots importRoots =
-        ImportRoots.builder(workspaceRoot, BuildSystem.Blaze).add(projectViewSet).build();
-    return addImportRoots(importRoots);
-  }
-
-  @Override
-  public boolean exists(WorkspaceRoot workspaceRoot, WorkspacePath file) {
-    return files.contains(file);
-  }
-}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/model/primitives/RuleNameTest.java b/base/tests/unittests/com/google/idea/blaze/base/model/primitives/RuleNameTest.java
deleted file mode 100644
index 1fbb704..0000000
--- a/base/tests/unittests/com/google/idea/blaze/base/model/primitives/RuleNameTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2016 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.idea.blaze.base.model.primitives;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.idea.blaze.base.BlazeTestCase;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Tests for rule name validation */
-@RunWith(JUnit4.class)
-public class RuleNameTest extends BlazeTestCase {
-
-  @Test
-  public void testValidateRuleName() {
-    // Legal names
-    assertThat(RuleName.validate("foo")).isTrue();
-    assertThat(RuleName.validate(".")).isTrue();
-    assertThat(RuleName.validate(".foo")).isTrue();
-    assertThat(RuleName.validate("foo+")).isTrue();
-    assertThat(RuleName.validate("_foo")).isTrue();
-    assertThat(RuleName.validate("-foo")).isTrue();
-    assertThat(RuleName.validate("foo-bar")).isTrue();
-    assertThat(RuleName.validate("foo..")).isTrue();
-    assertThat(RuleName.validate("..foo")).isTrue();
-
-    // Illegal names
-    assertThat(RuleName.validate("")).isFalse();
-    assertThat(RuleName.validate("/foo")).isFalse();
-    assertThat(RuleName.validate("../foo")).isFalse();
-    assertThat(RuleName.validate("./foo")).isFalse();
-    assertThat(RuleName.validate("..")).isFalse();
-    assertThat(RuleName.validate("foo/../bar")).isFalse();
-    assertThat(RuleName.validate("foo/./bar")).isFalse();
-    assertThat(RuleName.validate("foo//bar")).isFalse();
-    assertThat(RuleName.validate("foo/..")).isFalse();
-    assertThat(RuleName.validate("/..")).isFalse();
-    assertThat(RuleName.validate("foo/")).isFalse();
-    assertThat(RuleName.validate("/")).isFalse();
-  }
-}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/model/primitives/TargetNameTest.java b/base/tests/unittests/com/google/idea/blaze/base/model/primitives/TargetNameTest.java
new file mode 100644
index 0000000..b032e54
--- /dev/null
+++ b/base/tests/unittests/com/google/idea/blaze/base/model/primitives/TargetNameTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.model.primitives;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.idea.blaze.base.BlazeTestCase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for target name validation */
+@RunWith(JUnit4.class)
+public class TargetNameTest extends BlazeTestCase {
+
+  @Test
+  public void testValidateTargetName() {
+    // Legal names
+    assertThat(TargetName.validate("foo")).isTrue();
+    assertThat(TargetName.validate(".")).isTrue();
+    assertThat(TargetName.validate(".foo")).isTrue();
+    assertThat(TargetName.validate("foo+")).isTrue();
+    assertThat(TargetName.validate("_foo")).isTrue();
+    assertThat(TargetName.validate("-foo")).isTrue();
+    assertThat(TargetName.validate("foo-bar")).isTrue();
+    assertThat(TargetName.validate("foo..")).isTrue();
+    assertThat(TargetName.validate("..foo")).isTrue();
+
+    // Illegal names
+    assertThat(TargetName.validate("")).isFalse();
+    assertThat(TargetName.validate("/foo")).isFalse();
+    assertThat(TargetName.validate("../foo")).isFalse();
+    assertThat(TargetName.validate("./foo")).isFalse();
+    assertThat(TargetName.validate("..")).isFalse();
+    assertThat(TargetName.validate("foo/../bar")).isFalse();
+    assertThat(TargetName.validate("foo/./bar")).isFalse();
+    assertThat(TargetName.validate("foo//bar")).isFalse();
+    assertThat(TargetName.validate("foo/..")).isFalse();
+    assertThat(TargetName.validate("/..")).isFalse();
+    assertThat(TargetName.validate("foo/")).isFalse();
+    assertThat(TargetName.validate("/")).isFalse();
+  }
+}
diff --git a/base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewVerifierTest.java b/base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewVerifierTest.java
index 78506ef..4d27c8e 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewVerifierTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/projectview/ProjectViewVerifierTest.java
@@ -16,9 +16,9 @@
 package com.google.idea.blaze.base.projectview;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import com.google.idea.blaze.base.BlazeTestCase;
-import com.google.idea.blaze.base.io.MockWorkspaceScanner;
-import com.google.idea.blaze.base.io.WorkspaceScanner;
+import com.google.idea.blaze.base.io.FileAttributeProvider;
 import com.google.idea.blaze.base.model.primitives.LanguageClass;
 import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
@@ -29,9 +29,12 @@
 import com.google.idea.blaze.base.scope.BlazeContext;
 import com.google.idea.blaze.base.scope.ErrorCollector;
 import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
 import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.google.idea.blaze.base.sync.projectview.ImportRoots;
 import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
 import java.io.File;
+import java.util.Set;
 import org.jetbrains.annotations.NotNull;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -43,7 +46,7 @@
 
   private static final String FAKE_ROOT = "/root";
   private WorkspaceRoot workspaceRoot = new WorkspaceRoot(new File(FAKE_ROOT));
-  private MockWorkspaceScanner workspaceScanner;
+  private MockFileAttributeProvider fileAttributeProvider;
   private ErrorCollector errorCollector = new ErrorCollector();
   private BlazeContext context;
   private WorkspaceLanguageSettings workspaceLanguageSettings =
@@ -55,8 +58,8 @@
     super.initTest(applicationServices, projectServices);
     registerExtensionPoint(BlazeSyncPlugin.EP_NAME, BlazeSyncPlugin.class);
 
-    workspaceScanner = new MockWorkspaceScanner();
-    applicationServices.register(WorkspaceScanner.class, workspaceScanner);
+    fileAttributeProvider = new MockFileAttributeProvider(workspaceRoot);
+    applicationServices.register(FileAttributeProvider.class, fileAttributeProvider);
     context = new BlazeContext();
     context.addOutputSink(IssueOutput.class, errorCollector);
   }
@@ -77,7 +80,7 @@
                                     new WorkspacePath("java/com/google/android/apps/example2"))))
                     .build())
             .build();
-    workspaceScanner.addProjectView(workspaceRoot, projectViewSet);
+    fileAttributeProvider.addProjectView(projectViewSet);
     ProjectViewVerifier.verifyProjectView(
         context, workspaceRoot, projectViewSet, workspaceLanguageSettings);
     errorCollector.assertNoIssues();
@@ -99,7 +102,7 @@
                                     new WorkspacePath("java/com/google/android/apps/example"))))
                     .build())
             .build();
-    workspaceScanner.addProjectView(workspaceRoot, projectViewSet);
+    fileAttributeProvider.addProjectView(projectViewSet);
     ProjectViewVerifier.verifyProjectView(
         context, workspaceRoot, projectViewSet, workspaceLanguageSettings);
     errorCollector.assertIssues(
@@ -123,7 +126,7 @@
                                     new WorkspacePath("java/com/google/android/apps"))))
                     .build())
             .build();
-    workspaceScanner.addProjectView(workspaceRoot, projectViewSet);
+    fileAttributeProvider.addProjectView(projectViewSet);
     ProjectViewVerifier.verifyProjectView(
         context, workspaceRoot, projectViewSet, workspaceLanguageSettings);
     errorCollector.assertIssues(
@@ -148,7 +151,7 @@
                                         "java/com/google/android/apps/example/subdir"))))
                     .build())
             .build();
-    workspaceScanner.addProjectView(workspaceRoot, projectViewSet);
+    fileAttributeProvider.addProjectView(projectViewSet);
     ProjectViewVerifier.verifyProjectView(
         context, workspaceRoot, projectViewSet, workspaceLanguageSettings);
     errorCollector.assertNoIssues();
@@ -174,4 +177,56 @@
             "Directory '%s' specified in import roots not found under workspace root '%s'",
             "java/com/google/android/apps/example", "/root"));
   }
+
+  static class MockFileAttributeProvider extends FileAttributeProvider {
+
+    private final WorkspaceRoot workspaceRoot;
+    private final Set<File> files = Sets.newHashSet();
+    private final Set<File> directories = Sets.newHashSet();
+
+    MockFileAttributeProvider(WorkspaceRoot workspaceRoot) {
+      this.workspaceRoot = workspaceRoot;
+    }
+
+    MockFileAttributeProvider addFile(WorkspacePath file) {
+      files.add(workspaceRoot.fileForPath(file));
+      return this;
+    }
+
+    MockFileAttributeProvider addDirectory(WorkspacePath file) {
+      addFile(file);
+      directories.add(workspaceRoot.fileForPath(file));
+      return this;
+    }
+
+    MockFileAttributeProvider addPackage(WorkspacePath file) {
+      addFile(new WorkspacePath(file + "/BUILD"));
+      addDirectory(file);
+      return this;
+    }
+
+    MockFileAttributeProvider addPackages(Iterable<WorkspacePath> files) {
+      for (WorkspacePath workspacePath : files) {
+        addPackage(workspacePath);
+      }
+      return this;
+    }
+
+    MockFileAttributeProvider addImportRoots(ImportRoots importRoots) {
+      addPackages(importRoots.rootDirectories());
+      addPackages(importRoots.excludeDirectories());
+      return this;
+    }
+
+    MockFileAttributeProvider addProjectView(ProjectViewSet projectViewSet) {
+      ImportRoots importRoots =
+          ImportRoots.builder(workspaceRoot, BuildSystem.Blaze).add(projectViewSet).build();
+      return addImportRoots(importRoots);
+    }
+
+    @Override
+    public boolean exists(File file) {
+      return files.contains(file);
+    }
+  }
 }
diff --git a/base/tests/unittests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationTest.java b/base/tests/unittests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationTest.java
index b8f8b22..2f1cac6 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/run/BlazeCommandRunConfigurationTest.java
@@ -20,10 +20,10 @@
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.idea.blaze.base.BlazeTestCase;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandlerProvider;
-import com.google.idea.blaze.base.run.rulefinder.RuleFinder;
+import com.google.idea.blaze.base.run.targetfinder.TargetFinder;
 import com.google.idea.blaze.base.settings.Blaze;
 import com.google.idea.blaze.base.settings.BlazeImportSettings;
 import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
@@ -59,7 +59,7 @@
     BlazeImportSettingsManager.getInstance(getProject()).setImportSettings(DUMMY_IMPORT_SETTINGS);
 
     applicationServices.register(ExperimentService.class, new MockExperimentService());
-    applicationServices.register(RuleFinder.class, new MockRuleFinder());
+    applicationServices.register(TargetFinder.class, new MockTargetFinder());
     ExtensionPointImpl<BlazeCommandRunConfigurationHandlerProvider> handlerProviderEp =
         registerExtensionPoint(
             BlazeCommandRunConfigurationHandlerProvider.EP_NAME,
@@ -94,9 +94,9 @@
     assertThat(readConfiguration.getTarget()).isEqualTo(configuration.getTarget());
   }
 
-  private static class MockRuleFinder extends RuleFinder {
+  private static class MockTargetFinder extends TargetFinder {
     @Override
-    public List<RuleIdeInfo> findRules(Project project, Predicate<RuleIdeInfo> predicate) {
+    public List<TargetIdeInfo> findTargets(Project project, Predicate<TargetIdeInfo> predicate) {
       return ImmutableList.of();
     }
   }
diff --git a/base/tests/unittests/com/google/idea/blaze/base/run/RuleNameHeuristicTest.java b/base/tests/unittests/com/google/idea/blaze/base/run/RuleNameHeuristicTest.java
index 1dbec9d..2923947 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/run/RuleNameHeuristicTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/run/RuleNameHeuristicTest.java
@@ -19,7 +19,7 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.idea.blaze.base.BlazeTestCase;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
 import java.io.File;
@@ -28,7 +28,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-/** Tests for {@link RuleNameHeuristic}. */
+/** Tests for {@link TargetNameHeuristic}. */
 @RunWith(JUnit4.class)
 public class RuleNameHeuristicTest extends BlazeTestCase {
 
@@ -36,79 +36,82 @@
   protected void initTest(Container applicationServices, Container projectServices) {
     super.initTest(applicationServices, projectServices);
 
-    ExtensionPointImpl<TestRuleHeuristic> ep =
-        registerExtensionPoint(TestRuleHeuristic.EP_NAME, TestRuleHeuristic.class);
-    ep.registerExtension(new RuleNameHeuristic());
+    ExtensionPointImpl<TestTargetHeuristic> ep =
+        registerExtensionPoint(TestTargetHeuristic.EP_NAME, TestTargetHeuristic.class);
+    ep.registerExtension(new TargetNameHeuristic());
   }
 
   @Test
   public void testPredicateMatchingName() throws Exception {
     File source = new File("java/com/foo/FooTest.java");
-    RuleIdeInfo rule = RuleIdeInfo.builder().setLabel("//foo:FooTest").setKind("java_test").build();
-    assertThat(new RuleNameHeuristic().matchesSource(rule, source, null)).isTrue();
+    TargetIdeInfo target =
+        TargetIdeInfo.builder().setLabel("//foo:FooTest").setKind("java_test").build();
+    assertThat(new TargetNameHeuristic().matchesSource(target, source, null)).isTrue();
   }
 
   @Test
   public void testPredicateMatchingNameAndPath() throws Exception {
     File source = new File("java/com/foo/FooTest.java");
-    RuleIdeInfo rule =
-        RuleIdeInfo.builder().setLabel("//foo:foo/FooTest").setKind("java_test").build();
-    assertThat(new RuleNameHeuristic().matchesSource(rule, source, null)).isTrue();
+    TargetIdeInfo target =
+        TargetIdeInfo.builder().setLabel("//foo:foo/FooTest").setKind("java_test").build();
+    assertThat(new TargetNameHeuristic().matchesSource(target, source, null)).isTrue();
   }
 
   @Test
   public void testPredicateNotMatchingForPartialOverlap() throws Exception {
     File source = new File("java/com/foo/BarFooTest.java");
-    RuleIdeInfo rule = RuleIdeInfo.builder().setLabel("//foo:FooTest").setKind("java_test").build();
-    assertThat(new RuleNameHeuristic().matchesSource(rule, source, null)).isFalse();
+    TargetIdeInfo target =
+        TargetIdeInfo.builder().setLabel("//foo:FooTest").setKind("java_test").build();
+    assertThat(new TargetNameHeuristic().matchesSource(target, source, null)).isFalse();
   }
 
   @Test
   public void testPredicateNotMatchingIncorrectPath() throws Exception {
     File source = new File("java/com/foo/FooTest.java");
-    RuleIdeInfo rule =
-        RuleIdeInfo.builder().setLabel("//foo:bar/FooTest").setKind("java_test").build();
-    assertThat(new RuleNameHeuristic().matchesSource(rule, source, null)).isFalse();
+    TargetIdeInfo target =
+        TargetIdeInfo.builder().setLabel("//foo:bar/FooTest").setKind("java_test").build();
+    assertThat(new TargetNameHeuristic().matchesSource(target, source, null)).isFalse();
   }
 
   @Test
   public void testPredicateDifferentName() throws Exception {
     File source = new File("java/com/foo/FooTest.java");
-    RuleIdeInfo rule = RuleIdeInfo.builder().setLabel("//foo:ForTest").setKind("java_test").build();
-    assertThat(new RuleNameHeuristic().matchesSource(rule, source, null)).isFalse();
+    TargetIdeInfo target =
+        TargetIdeInfo.builder().setLabel("//foo:ForTest").setKind("java_test").build();
+    assertThat(new TargetNameHeuristic().matchesSource(target, source, null)).isFalse();
   }
 
   @Test
   public void testFilterNoMatchesFallBackToFirstRule() throws Exception {
     File source = new File("java/com/foo/FooTest.java");
-    Collection<RuleIdeInfo> rules =
+    Collection<TargetIdeInfo> targets =
         ImmutableList.of(
-            RuleIdeInfo.builder().setLabel("//foo:FirstTest").setKind("java_test").build(),
-            RuleIdeInfo.builder().setLabel("//bar:OtherTest").setKind("java_test").build());
-    Label match = TestRuleHeuristic.chooseTestTargetForSourceFile(source, rules, null);
+            TargetIdeInfo.builder().setLabel("//foo:FirstTest").setKind("java_test").build(),
+            TargetIdeInfo.builder().setLabel("//bar:OtherTest").setKind("java_test").build());
+    Label match = TestTargetHeuristic.chooseTestTargetForSourceFile(source, targets, null);
     assertThat(match).isEqualTo(new Label("//foo:FirstTest"));
   }
 
   @Test
   public void testFilterOneMatch() throws Exception {
     File source = new File("java/com/foo/FooTest.java");
-    Collection<RuleIdeInfo> rules =
+    Collection<TargetIdeInfo> targets =
         ImmutableList.of(
-            RuleIdeInfo.builder().setLabel("//bar:FirstTest").setKind("java_test").build(),
-            RuleIdeInfo.builder().setLabel("//foo:FooTest").setKind("java_test").build());
-    Label match = TestRuleHeuristic.chooseTestTargetForSourceFile(source, rules, null);
+            TargetIdeInfo.builder().setLabel("//bar:FirstTest").setKind("java_test").build(),
+            TargetIdeInfo.builder().setLabel("//foo:FooTest").setKind("java_test").build());
+    Label match = TestTargetHeuristic.chooseTestTargetForSourceFile(source, targets, null);
     assertThat(match).isEqualTo(new Label("//foo:FooTest"));
   }
 
   @Test
   public void testFilterChoosesFirstMatch() throws Exception {
     File source = new File("java/com/foo/FooTest.java");
-    Collection<RuleIdeInfo> rules =
+    Collection<TargetIdeInfo> targets =
         ImmutableList.of(
-            RuleIdeInfo.builder().setLabel("//bar:OtherTest").setKind("java_test").build(),
-            RuleIdeInfo.builder().setLabel("//foo:FooTest").setKind("java_test").build(),
-            RuleIdeInfo.builder().setLabel("//bar/foo:FooTest").setKind("java_test").build());
-    Label match = TestRuleHeuristic.chooseTestTargetForSourceFile(source, rules, null);
+            TargetIdeInfo.builder().setLabel("//bar:OtherTest").setKind("java_test").build(),
+            TargetIdeInfo.builder().setLabel("//foo:FooTest").setKind("java_test").build(),
+            TargetIdeInfo.builder().setLabel("//bar/foo:FooTest").setKind("java_test").build());
+    Label match = TestTargetHeuristic.chooseTestTargetForSourceFile(source, targets, null);
     assertThat(match).isEqualTo(new Label("//foo:FooTest"));
   }
 }
diff --git a/base/tests/unittests/com/google/idea/blaze/base/run/TestSizeHeuristicTest.java b/base/tests/unittests/com/google/idea/blaze/base/run/TestSizeHeuristicTest.java
index f75be8a..99ce084 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/run/TestSizeHeuristicTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/run/TestSizeHeuristicTest.java
@@ -19,7 +19,7 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.idea.blaze.base.BlazeTestCase;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.ideinfo.TestIdeInfo;
 import com.google.idea.blaze.base.ideinfo.TestIdeInfo.TestSize;
 import com.google.idea.blaze.base.model.primitives.Label;
@@ -37,119 +37,119 @@
   protected void initTest(Container applicationServices, Container projectServices) {
     super.initTest(applicationServices, projectServices);
 
-    ExtensionPointImpl<TestRuleHeuristic> ep =
-        registerExtensionPoint(TestRuleHeuristic.EP_NAME, TestRuleHeuristic.class);
+    ExtensionPointImpl<TestTargetHeuristic> ep =
+        registerExtensionPoint(TestTargetHeuristic.EP_NAME, TestTargetHeuristic.class);
     ep.registerExtension(new TestSizeHeuristic());
   }
 
   @Test
   public void testPredicateMatchingSize() throws Exception {
     File source = new File("java/com/foo/FooTest.java");
-    RuleIdeInfo rule =
-        RuleIdeInfo.builder()
+    TargetIdeInfo target =
+        TargetIdeInfo.builder()
             .setLabel("//foo:test")
             .setKind("java_test")
             .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.MEDIUM))
             .build();
-    assertThat(new TestSizeHeuristic().matchesSource(rule, source, TestSize.MEDIUM)).isTrue();
+    assertThat(new TestSizeHeuristic().matchesSource(target, source, TestSize.MEDIUM)).isTrue();
   }
 
   @Test
   public void testPredicateDifferentSize() throws Exception {
     File source = new File("java/com/foo/FooTest.java");
-    RuleIdeInfo rule =
-        RuleIdeInfo.builder()
+    TargetIdeInfo target =
+        TargetIdeInfo.builder()
             .setLabel("//foo:test")
             .setKind("java_test")
             .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.MEDIUM))
             .build();
-    assertThat(new TestSizeHeuristic().matchesSource(rule, source, TestSize.SMALL)).isFalse();
+    assertThat(new TestSizeHeuristic().matchesSource(target, source, TestSize.SMALL)).isFalse();
   }
 
   @Test
   public void testPredicateDefaultToSmallSize() throws Exception {
     File source = new File("java/com/foo/FooTest.java");
-    RuleIdeInfo rule =
-        RuleIdeInfo.builder()
+    TargetIdeInfo target =
+        TargetIdeInfo.builder()
             .setLabel("//foo:test")
             .setKind("java_test")
             .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.SMALL))
             .build();
-    assertThat(new TestSizeHeuristic().matchesSource(rule, source, null)).isTrue();
+    assertThat(new TestSizeHeuristic().matchesSource(target, source, null)).isTrue();
 
-    rule =
-        RuleIdeInfo.builder()
+    target =
+        TargetIdeInfo.builder()
             .setLabel("//foo:test")
             .setKind("java_test")
             .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.MEDIUM))
             .build();
-    assertThat(new TestSizeHeuristic().matchesSource(rule, source, null)).isFalse();
+    assertThat(new TestSizeHeuristic().matchesSource(target, source, null)).isFalse();
   }
 
   @Test
   public void testFilterNoMatchesFallBackToFirstRule() throws Exception {
     File source = new File("java/com/foo/FooTest.java");
-    ImmutableList<RuleIdeInfo> rules =
+    ImmutableList<TargetIdeInfo> rules =
         ImmutableList.of(
-            RuleIdeInfo.builder()
+            TargetIdeInfo.builder()
                 .setLabel("//foo:test1")
                 .setKind("java_test")
                 .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.MEDIUM))
                 .build(),
-            RuleIdeInfo.builder()
+            TargetIdeInfo.builder()
                 .setLabel("//foo:test2")
                 .setKind("java_test")
                 .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.LARGE))
                 .build(),
-            RuleIdeInfo.builder()
+            TargetIdeInfo.builder()
                 .setLabel("//foo:test3")
                 .setKind("java_test")
                 .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.ENORMOUS))
                 .build());
-    Label match = TestRuleHeuristic.chooseTestTargetForSourceFile(source, rules, TestSize.SMALL);
+    Label match = TestTargetHeuristic.chooseTestTargetForSourceFile(source, rules, TestSize.SMALL);
     assertThat(match).isEqualTo(new Label("//foo:test1"));
   }
 
   @Test
   public void testFilterOneMatch() throws Exception {
     File source = new File("java/com/foo/FooTest.java");
-    ImmutableList<RuleIdeInfo> rules =
+    ImmutableList<TargetIdeInfo> rules =
         ImmutableList.of(
-            RuleIdeInfo.builder()
+            TargetIdeInfo.builder()
                 .setLabel("//foo:test1")
                 .setKind("java_test")
                 .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.MEDIUM))
                 .build(),
-            RuleIdeInfo.builder()
+            TargetIdeInfo.builder()
                 .setLabel("//foo:test2")
                 .setKind("java_test")
                 .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.SMALL))
                 .build());
-    Label match = TestRuleHeuristic.chooseTestTargetForSourceFile(source, rules, TestSize.SMALL);
+    Label match = TestTargetHeuristic.chooseTestTargetForSourceFile(source, rules, TestSize.SMALL);
     assertThat(match).isEqualTo(new Label("//foo:test2"));
   }
 
   @Test
   public void testFilterChoosesFirstMatch() throws Exception {
     File source = new File("java/com/foo/FooTest.java");
-    ImmutableList<RuleIdeInfo> rules =
+    ImmutableList<TargetIdeInfo> rules =
         ImmutableList.of(
-            RuleIdeInfo.builder()
+            TargetIdeInfo.builder()
                 .setLabel("//foo:test1")
                 .setKind("java_test")
                 .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.MEDIUM))
                 .build(),
-            RuleIdeInfo.builder()
+            TargetIdeInfo.builder()
                 .setLabel("//foo:test2")
                 .setKind("java_test")
                 .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.SMALL))
                 .build(),
-            RuleIdeInfo.builder()
+            TargetIdeInfo.builder()
                 .setLabel("//foo:test3")
                 .setKind("java_test")
                 .setTestInfo(TestIdeInfo.builder().setTestSize(TestSize.SMALL))
                 .build());
-    Label match = TestRuleHeuristic.chooseTestTargetForSourceFile(source, rules, TestSize.SMALL);
+    Label match = TestTargetHeuristic.chooseTestTargetForSourceFile(source, rules, TestSize.SMALL);
     assertThat(match).isEqualTo(new Label("//foo:test2"));
   }
 }
diff --git a/base/tests/unittests/com/google/idea/blaze/base/run/state/BlazeCommandRunConfigurationCommonStateTest.java b/base/tests/unittests/com/google/idea/blaze/base/run/state/BlazeCommandRunConfigurationCommonStateTest.java
index 307722c..c8eaeec 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/run/state/BlazeCommandRunConfigurationCommonStateTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/run/state/BlazeCommandRunConfigurationCommonStateTest.java
@@ -26,6 +26,8 @@
 import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
 import com.intellij.ide.ui.UISettings;
 import org.jdom.Element;
+import org.jdom.output.Format;
+import org.jdom.output.XMLOutputter;
 import org.jetbrains.annotations.NotNull;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -102,6 +104,24 @@
   }
 
   @Test
+  public void repeatedWriteShouldNotChangeElement() throws Exception {
+    final XMLOutputter xmlOutputter = new XMLOutputter(Format.getCompactFormat());
+
+    state.setCommand(COMMAND);
+    state.setBlazeFlags(ImmutableList.of("--flag1", "--flag2"));
+    state.setExeFlags(ImmutableList.of("--exeFlag1"));
+    state.setBlazeBinary("/usr/bin/blaze");
+
+    Element firstWrite = new Element("test");
+    state.writeExternal(firstWrite);
+    Element secondWrite = firstWrite.clone();
+    state.writeExternal(secondWrite);
+
+    assertThat(xmlOutputter.outputString(secondWrite))
+        .isEqualTo(xmlOutputter.outputString(firstWrite));
+  }
+
+  @Test
   public void editorApplyToAndResetFromShouldMatch() throws Exception {
     RunConfigurationStateEditor editor = state.getEditor(project);
 
diff --git a/base/tests/unittests/com/google/idea/blaze/base/run/testmap/TestMapTest.java b/base/tests/unittests/com/google/idea/blaze/base/run/testmap/TestMapTest.java
index b3dfb93..9bea7c2 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/run/testmap/TestMapTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/run/testmap/TestMapTest.java
@@ -20,14 +20,14 @@
 import com.google.common.collect.ImmutableMultimap;
 import com.google.idea.blaze.base.BlazeTestCase;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
-import com.google.idea.blaze.base.ideinfo.RuleMapBuilder;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
+import com.google.idea.blaze.base.ideinfo.TargetMapBuilder;
 import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.rulemaps.ReverseDependencyMap;
-import com.google.idea.blaze.base.run.testmap.TestRuleFinderImpl.TestMap;
+import com.google.idea.blaze.base.run.testmap.TestTargetFilterImpl.TestMap;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
+import com.google.idea.blaze.base.targetmaps.ReverseDependencyMap;
 import com.google.idea.common.experiments.ExperimentService;
 import com.google.idea.common.experiments.MockExperimentService;
 import java.io.File;
@@ -39,7 +39,7 @@
 /** Tests for the test map */
 @RunWith(JUnit4.class)
 public class TestMapTest extends BlazeTestCase {
-  private RuleMapBuilder ruleMapBuilder;
+  private TargetMapBuilder targetMapBuilder;
 
   private final ArtifactLocationDecoder artifactLocationDecoder =
       (ArtifactLocationDecoder)
@@ -50,117 +50,117 @@
       @NotNull Container applicationServices, @NotNull Container projectServices) {
     super.initTest(applicationServices, projectServices);
     applicationServices.register(ExperimentService.class, new MockExperimentService());
-    ruleMapBuilder = RuleMapBuilder.builder();
+    targetMapBuilder = TargetMapBuilder.builder();
   }
 
   @Test
   public void testTrivialTestMap() throws Exception {
-    RuleMap ruleMap =
-        ruleMapBuilder
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMap targetMap =
+        targetMapBuilder
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//test:test")
                     .setKind("java_test")
                     .addSource(sourceRoot("test/Test.java")))
             .build();
 
-    TestMap testMap = new TestMap(project, artifactLocationDecoder, ruleMap);
-    ImmutableMultimap<RuleKey, RuleKey> reverseDependencies =
-        ReverseDependencyMap.createRdepsMap(ruleMap);
+    TestMap testMap = new TestMap(project, artifactLocationDecoder, targetMap);
+    ImmutableMultimap<TargetKey, TargetKey> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(targetMap);
     assertThat(testMap.testTargetsForSourceFile(reverseDependencies, new File("/test/Test.java")))
         .containsExactly(new Label("//test:test"));
   }
 
   @Test
   public void testOneStepRemovedTestMap() throws Exception {
-    RuleMap ruleMap =
-        ruleMapBuilder
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMap targetMap =
+        targetMapBuilder
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//test:test")
                     .setKind("java_test")
                     .addDependency("//test:lib"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//test:lib")
                     .setKind("java_library")
                     .addSource(sourceRoot("test/Test.java")))
             .build();
 
-    TestMap testMap = new TestMap(project, artifactLocationDecoder, ruleMap);
-    ImmutableMultimap<RuleKey, RuleKey> reverseDependencies =
-        ReverseDependencyMap.createRdepsMap(ruleMap);
+    TestMap testMap = new TestMap(project, artifactLocationDecoder, targetMap);
+    ImmutableMultimap<TargetKey, TargetKey> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(targetMap);
     assertThat(testMap.testTargetsForSourceFile(reverseDependencies, new File("/test/Test.java")))
         .containsExactly(new Label("//test:test"));
   }
 
   @Test
   public void testTwoCandidatesTestMap() throws Exception {
-    RuleMap ruleMap =
-        ruleMapBuilder
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMap targetMap =
+        targetMapBuilder
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//test:test")
                     .setKind("java_test")
                     .addDependency("//test:lib"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//test:test2")
                     .setKind("java_test")
                     .addDependency("//test:lib"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//test:lib")
                     .setKind("java_library")
                     .addSource(sourceRoot("test/Test.java")))
             .build();
 
-    TestMap testMap = new TestMap(project, artifactLocationDecoder, ruleMap);
-    ImmutableMultimap<RuleKey, RuleKey> reverseDependencies =
-        ReverseDependencyMap.createRdepsMap(ruleMap);
+    TestMap testMap = new TestMap(project, artifactLocationDecoder, targetMap);
+    ImmutableMultimap<TargetKey, TargetKey> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(targetMap);
     assertThat(testMap.testTargetsForSourceFile(reverseDependencies, new File("/test/Test.java")))
         .containsExactly(new Label("//test:test"), new Label("//test:test2"));
   }
 
   @Test
   public void testBfsPreferred() throws Exception {
-    RuleMap ruleMap =
-        ruleMapBuilder
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMap targetMap =
+        targetMapBuilder
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//test:lib")
                     .setKind("java_library")
                     .addSource(sourceRoot("test/Test.java")))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//test:lib2")
                     .setKind("java_library")
                     .addDependency("//test:lib"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//test:test2")
                     .setKind("java_test")
                     .addDependency("//test:lib2"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//test:test")
                     .setKind("java_test")
                     .addDependency("//test:lib"))
             .build();
 
-    TestMap testMap = new TestMap(project, artifactLocationDecoder, ruleMap);
-    ImmutableMultimap<RuleKey, RuleKey> reverseDependencies =
-        ReverseDependencyMap.createRdepsMap(ruleMap);
+    TestMap testMap = new TestMap(project, artifactLocationDecoder, targetMap);
+    ImmutableMultimap<TargetKey, TargetKey> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(targetMap);
     assertThat(testMap.testTargetsForSourceFile(reverseDependencies, new File("/test/Test.java")))
         .containsExactly(new Label("//test:test"), new Label("//test:test2"))
         .inOrder();
@@ -168,69 +168,69 @@
 
   @Test
   public void testSourceIncludedMultipleTimesFindsAll() throws Exception {
-    RuleMap ruleMap =
-        ruleMapBuilder
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMap targetMap =
+        targetMapBuilder
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//test:test")
                     .setKind("java_test")
                     .addDependency("//test:lib"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//test:test2")
                     .setKind("java_test")
                     .addDependency("//test:lib2"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//test:lib")
                     .setKind("java_library")
                     .addSource(sourceRoot("test/Test.java")))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//test:lib2")
                     .setKind("java_library")
                     .addSource(sourceRoot("test/Test.java")))
             .build();
 
-    TestMap testMap = new TestMap(project, artifactLocationDecoder, ruleMap);
-    ImmutableMultimap<RuleKey, RuleKey> reverseDependencies =
-        ReverseDependencyMap.createRdepsMap(ruleMap);
+    TestMap testMap = new TestMap(project, artifactLocationDecoder, targetMap);
+    ImmutableMultimap<TargetKey, TargetKey> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(targetMap);
     assertThat(testMap.testTargetsForSourceFile(reverseDependencies, new File("/test/Test.java")))
         .containsExactly(new Label("//test:test"), new Label("//test:test2"));
   }
 
   @Test
   public void testSourceIncludedMultipleTimesShouldOnlyGiveOneInstanceOfTest() throws Exception {
-    RuleMap ruleMap =
-        ruleMapBuilder
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMap targetMap =
+        targetMapBuilder
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//test:test")
                     .setKind("java_test")
                     .addDependency("//test:lib")
                     .addDependency("//test:lib2"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//test:lib")
                     .setKind("java_library")
                     .addSource(sourceRoot("test/Test.java")))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//test:lib2")
                     .setKind("java_library")
                     .addSource(sourceRoot("test/Test.java")))
             .build();
 
-    TestMap testMap = new TestMap(project, artifactLocationDecoder, ruleMap);
-    ImmutableMultimap<RuleKey, RuleKey> reverseDependencies =
-        ReverseDependencyMap.createRdepsMap(ruleMap);
+    TestMap testMap = new TestMap(project, artifactLocationDecoder, targetMap);
+    ImmutableMultimap<TargetKey, TargetKey> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(targetMap);
     assertThat(testMap.testTargetsForSourceFile(reverseDependencies, new File("/test/Test.java")))
         .containsExactly(new Label("//test:test"));
   }
diff --git a/base/tests/unittests/com/google/idea/blaze/base/sync/LanguageSupportTest.java b/base/tests/unittests/com/google/idea/blaze/base/sync/LanguageSupportTest.java
index 33f2ef4..af61e7f 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/sync/LanguageSupportTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/sync/LanguageSupportTest.java
@@ -17,13 +17,16 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.idea.blaze.base.BlazeTestCase;
 import com.google.idea.blaze.base.model.primitives.LanguageClass;
 import com.google.idea.blaze.base.model.primitives.WorkspaceType;
 import com.google.idea.blaze.base.projectview.ProjectView;
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.projectview.section.ListSection;
 import com.google.idea.blaze.base.projectview.section.ScalarSection;
+import com.google.idea.blaze.base.projectview.section.sections.AdditionalLanguagesSection;
 import com.google.idea.blaze.base.projectview.section.sections.WorkspaceTypeSection;
 import com.google.idea.blaze.base.scope.BlazeContext;
 import com.google.idea.blaze.base.scope.ErrorCollector;
@@ -63,6 +66,11 @@
           public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
             return ImmutableSet.of(LanguageClass.C);
           }
+
+          @Override
+          public ImmutableList<WorkspaceType> getSupportedWorkspaceTypes() {
+            return ImmutableList.of(WorkspaceType.C);
+          }
         });
 
     ProjectViewSet projectViewSet =
@@ -77,11 +85,12 @@
     errorCollector.assertNoIssues();
     assertThat(workspaceLanguageSettings)
         .isEqualTo(
-            new WorkspaceLanguageSettings(WorkspaceType.C, ImmutableSet.of(LanguageClass.C)));
+            new WorkspaceLanguageSettings(
+                WorkspaceType.C, ImmutableSet.of(LanguageClass.C, LanguageClass.GENERIC)));
   }
 
   @Test
-  public void testFailWithUnsupportedLanguage() {
+  public void testFailWithUnsupportedWorkspaceType() {
     ProjectViewSet projectViewSet =
         ProjectViewSet.builder()
             .add(
@@ -90,8 +99,36 @@
                     .build())
             .build();
     LanguageSupport.createWorkspaceLanguageSettings(context, projectViewSet);
-    errorCollector.assertIssues(
-        "Language 'c' is not supported for this plugin with workspace type: 'c'");
+    errorCollector.assertIssues("Workspace type 'c' is not supported by this plugin");
+  }
+
+  @Test
+  public void testFailWithUnsupportedLanguageType() {
+    syncPlugins.registerExtension(
+        new BlazeSyncPlugin.Adapter() {
+          @Override
+          public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
+            return ImmutableSet.of(LanguageClass.C);
+          }
+
+          @Override
+          public ImmutableList<WorkspaceType> getSupportedWorkspaceTypes() {
+            return ImmutableList.of(WorkspaceType.C);
+          }
+        });
+
+    ProjectViewSet projectViewSet =
+        ProjectViewSet.builder()
+            .add(
+                ProjectView.builder()
+                    .add(ScalarSection.builder(WorkspaceTypeSection.KEY).set(WorkspaceType.C))
+                    .add(
+                        ListSection.builder(AdditionalLanguagesSection.KEY)
+                            .add(LanguageClass.PYTHON))
+                    .build())
+            .build();
+    LanguageSupport.createWorkspaceLanguageSettings(context, projectViewSet);
+    errorCollector.assertIssues("Language 'python' is not supported by this plugin");
   }
 
   /** Tests that we ask for java and android when the workspace type is android. */
@@ -103,6 +140,11 @@
           public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
             return ImmutableSet.of(LanguageClass.ANDROID, LanguageClass.JAVA, LanguageClass.C);
           }
+
+          @Override
+          public ImmutableList<WorkspaceType> getSupportedWorkspaceTypes() {
+            return ImmutableList.of(WorkspaceType.ANDROID);
+          }
         });
 
     ProjectViewSet projectViewSet =
@@ -117,6 +159,7 @@
     assertThat(workspaceLanguageSettings)
         .isEqualTo(
             new WorkspaceLanguageSettings(
-                WorkspaceType.ANDROID, ImmutableSet.of(LanguageClass.JAVA, LanguageClass.ANDROID)));
+                WorkspaceType.ANDROID,
+                ImmutableSet.of(LanguageClass.JAVA, LanguageClass.ANDROID, LanguageClass.GENERIC)));
   }
 }
diff --git a/base/tests/unittests/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImplTest.java b/base/tests/unittests/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImplTest.java
index 7d005db..b064721 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImplTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/sync/aspects/BlazeIdeInterfaceAspectsImplTest.java
@@ -19,8 +19,8 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.idea.blaze.base.BlazeTestCase;
 import com.google.idea.blaze.base.TestUtils;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import com.google.idea.blaze.base.io.FileAttributeProvider;
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.model.primitives.LanguageClass;
@@ -28,7 +28,7 @@
 import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
 import com.google.idea.common.experiments.ExperimentService;
 import com.google.idea.common.experiments.MockExperimentService;
-import com.google.repackaged.devtools.build.lib.ideinfo.androidstudio.AndroidStudioIdeInfo;
+import com.google.repackaged.devtools.intellij.ideinfo.IntellijIdeInfo;
 import java.io.File;
 import org.jetbrains.annotations.NotNull;
 import org.junit.Test;
@@ -39,8 +39,6 @@
 @RunWith(JUnit4.class)
 public class BlazeIdeInterfaceAspectsImplTest extends BlazeTestCase {
 
-  private static final File DUMMY_ROOT = new File("/");
-
   @Override
   protected void initTest(
       @NotNull Container applicationServices, @NotNull Container projectServices) {
@@ -50,26 +48,26 @@
   }
 
   @Test
-  public void testRuleIdeInfoIsSerializable() {
-    AndroidStudioIdeInfo.RuleIdeInfo ideProto =
-        AndroidStudioIdeInfo.RuleIdeInfo.newBuilder()
+  public void testTargetIdeInfoIsSerializable() {
+    IntellijIdeInfo.TargetIdeInfo ideProto =
+        IntellijIdeInfo.TargetIdeInfo.newBuilder()
             .setLabel("//test:test")
             .setKindString("android_binary")
             .addDependencies("//test:dep")
             .addTags("tag")
-            .setJavaRuleIdeInfo(
-                AndroidStudioIdeInfo.JavaRuleIdeInfo.newBuilder()
+            .setJavaIdeInfo(
+                IntellijIdeInfo.JavaIdeInfo.newBuilder()
                     .addJars(
-                        AndroidStudioIdeInfo.LibraryArtifact.newBuilder()
+                        IntellijIdeInfo.LibraryArtifact.newBuilder()
                             .setJar(artifactLocation("jar.jar"))
                             .build())
                     .addGeneratedJars(
-                        AndroidStudioIdeInfo.LibraryArtifact.newBuilder()
+                        IntellijIdeInfo.LibraryArtifact.newBuilder()
                             .setJar(artifactLocation("jar.jar"))
                             .build())
                     .addSources(artifactLocation("source.java")))
-            .setAndroidRuleIdeInfo(
-                AndroidStudioIdeInfo.AndroidRuleIdeInfo.newBuilder()
+            .setAndroidIdeInfo(
+                IntellijIdeInfo.AndroidIdeInfo.newBuilder()
                     .addResources(artifactLocation("res"))
                     .setApk(artifactLocation("apk"))
                     .addDependencyApk(artifactLocation("apk"))
@@ -79,31 +77,26 @@
     WorkspaceLanguageSettings workspaceLanguageSettings =
         new WorkspaceLanguageSettings(
             WorkspaceType.ANDROID, ImmutableSet.of(LanguageClass.ANDROID));
-    RuleIdeInfo ruleIdeInfo =
-        IdeInfoFromProtobuf.makeRuleIdeInfo(workspaceLanguageSettings, ideProto);
-    TestUtils.assertIsSerializable(ruleIdeInfo);
+    TargetIdeInfo target =
+        IdeInfoFromProtobuf.makeTargetIdeInfo(workspaceLanguageSettings, ideProto);
+    TestUtils.assertIsSerializable(target);
   }
 
   @Test
   public void testBlazeStateIsSerializable() {
     BlazeIdeInterfaceAspectsImpl.State state = new BlazeIdeInterfaceAspectsImpl.State();
-    state.fileToRuleMapKey =
+    state.fileToTargetMapKey =
         ImmutableMap.of(
             new File("fileName"),
-            RuleIdeInfo.builder().setLabel(new Label("//test:test")).build().key);
+            TargetIdeInfo.builder().setLabel(new Label("//test:test")).build().key);
     state.fileState = ImmutableMap.of();
-    state.ruleMap =
-        new RuleMap(ImmutableMap.of()); // Tested separately in testRuleIdeInfoIsSerializable
+    state.targetMap =
+        new TargetMap(ImmutableMap.of()); // Tested separately in testRuleIdeInfoIsSerializable
 
     TestUtils.assertIsSerializable(state);
   }
 
-  static AndroidStudioIdeInfo.ArtifactLocation artifactLocation(String relativePath) {
-    return artifactLocation(DUMMY_ROOT.toString(), relativePath);
-  }
-
-  static AndroidStudioIdeInfo.ArtifactLocation artifactLocation(
-      String rootPath, String relativePath) {
-    return AndroidStudioIdeInfo.ArtifactLocation.newBuilder().setRelativePath(relativePath).build();
+  static IntellijIdeInfo.ArtifactLocation artifactLocation(String relativePath) {
+    return IntellijIdeInfo.ArtifactLocation.newBuilder().setRelativePath(relativePath).build();
   }
 }
diff --git a/base/tests/unittests/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoderTest.java b/base/tests/unittests/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoderTest.java
index 02849cd..a7c7816 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoderTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/sync/workspace/ArtifactLocationDecoderTest.java
@@ -38,6 +38,7 @@
 public class ArtifactLocationDecoderTest extends BlazeTestCase {
 
   private static final String EXECUTION_ROOT = "/path/to/_blaze_user/1234bf129e/root";
+  private static final String OUTPUT_BASE = "/path/to/_blaze_user/1234bf129e";
 
   static class MockFileAttributeProvider extends FileAttributeProvider {
     final Set<File> files = Sets.newHashSet();
@@ -76,7 +77,8 @@
             new File(EXECUTION_ROOT),
             packagePaths,
             new ExecutionRootPath("root/blaze-out/crosstool/bin"),
-            new ExecutionRootPath("root/blaze-out/crosstool/genfiles"));
+            new ExecutionRootPath("root/blaze-out/crosstool/genfiles"),
+            new File(OUTPUT_BASE));
 
     fileChecker.addFiles(
         new File("/path/to/com/google/Bla.java"),
@@ -124,10 +126,34 @@
                 new File(EXECUTION_ROOT),
                 ImmutableList.of(new File("/path/to/root")),
                 new ExecutionRootPath("root/blaze-out/crosstool/bin"),
-                new ExecutionRootPath("root/blaze-out/crosstool/genfiles")),
+                new ExecutionRootPath("root/blaze-out/crosstool/genfiles"),
+                new File(OUTPUT_BASE)),
             null);
 
     assertThat(decoder.decode(artifactLocation).getPath())
         .isEqualTo(EXECUTION_ROOT + "/blaze-out/bin/com/google/Bla.java");
   }
+
+  @Test
+  public void testExternalArtifact() throws Exception {
+    ArtifactLocation artifactLocation =
+        ArtifactLocation.builder()
+            .setRelativePath("external/com/google/Bla.java")
+            .setIsSource(true)
+            .setIsExternal(true)
+            .build();
+
+    ArtifactLocationDecoder decoder =
+        new ArtifactLocationDecoderImpl(
+            new BlazeRoots(
+                new File(EXECUTION_ROOT),
+                ImmutableList.of(new File("/path/to/root")),
+                new ExecutionRootPath("root/blaze-out/crosstool/bin"),
+                new ExecutionRootPath("root/blaze-out/crosstool/genfiles"),
+                new File(OUTPUT_BASE)),
+            null);
+
+    assertThat(decoder.decode(artifactLocation).getPath())
+        .isEqualTo(OUTPUT_BASE + "/external/com/google/Bla.java");
+  }
 }
diff --git a/base/tests/unittests/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverImplTest.java b/base/tests/unittests/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverImplTest.java
index 13b71f5..ff130fb 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverImplTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/sync/workspace/WorkspacePathResolverImplTest.java
@@ -37,7 +37,8 @@
           new File(EXECUTION_ROOT),
           ImmutableList.of(WORKSPACE_ROOT.directory()),
           new ExecutionRootPath("blaze-out/crosstool/bin"),
-          new ExecutionRootPath("blaze-out/crosstool/genfiles"));
+          new ExecutionRootPath("blaze-out/crosstool/genfiles"),
+          null);
 
   @Test
   public void testResolveToIncludeDirectories() {
diff --git a/base/tests/unittests/com/google/idea/blaze/base/rulemaps/ReverseDependencyMapTest.java b/base/tests/unittests/com/google/idea/blaze/base/targetmaps/ReverseDependencyMapTest.java
similarity index 61%
rename from base/tests/unittests/com/google/idea/blaze/base/rulemaps/ReverseDependencyMapTest.java
rename to base/tests/unittests/com/google/idea/blaze/base/targetmaps/ReverseDependencyMapTest.java
index 1dbbbce..574194c 100644
--- a/base/tests/unittests/com/google/idea/blaze/base/rulemaps/ReverseDependencyMapTest.java
+++ b/base/tests/unittests/com/google/idea/blaze/base/targetmaps/ReverseDependencyMapTest.java
@@ -13,17 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.google.idea.blaze.base.rulemaps;
+package com.google.idea.blaze.base.targetmaps;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.common.collect.ImmutableMultimap;
 import com.google.idea.blaze.base.BlazeTestCase;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
-import com.google.idea.blaze.base.ideinfo.RuleMapBuilder;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
+import com.google.idea.blaze.base.ideinfo.TargetMapBuilder;
 import com.google.idea.blaze.base.model.primitives.Label;
 import org.jetbrains.annotations.NotNull;
 import org.junit.Test;
@@ -41,156 +41,156 @@
 
   @Test
   public void testSingleDep() {
-    RuleMapBuilder builder = RuleMapBuilder.builder();
-    RuleMap ruleMap =
+    TargetMapBuilder builder = TargetMapBuilder.builder();
+    TargetMap targetMap =
         builder
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//l:l1")
                     .setKind("java_library")
                     .addDependency("//l:l2"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//l:l2")
                     .setKind("java_library"))
             .build();
 
-    ImmutableMultimap<RuleKey, RuleKey> reverseDependencies =
-        ReverseDependencyMap.createRdepsMap(ruleMap);
+    ImmutableMultimap<TargetKey, TargetKey> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(targetMap);
     assertThat(reverseDependencies)
         .containsEntry(
-            RuleKey.forPlainTarget(new Label("//l:l2")),
-            RuleKey.forPlainTarget(new Label("//l:l1")));
+            TargetKey.forPlainTarget(new Label("//l:l2")),
+            TargetKey.forPlainTarget(new Label("//l:l1")));
   }
 
   @Test
   public void testLabelDepsOnTwoLabels() {
-    RuleMapBuilder builder = RuleMapBuilder.builder();
-    RuleMap ruleMap =
+    TargetMapBuilder builder = TargetMapBuilder.builder();
+    TargetMap targetMap =
         builder
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//l:l1")
                     .setKind("java_library")
                     .addDependency("//l:l2")
                     .addDependency("//l:l3"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//l:l2")
                     .setKind("java_library"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//l:l3")
                     .setKind("java_library"))
             .build();
 
-    ImmutableMultimap<RuleKey, RuleKey> reverseDependencies =
-        ReverseDependencyMap.createRdepsMap(ruleMap);
+    ImmutableMultimap<TargetKey, TargetKey> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(targetMap);
     assertThat(reverseDependencies)
         .containsEntry(
-            RuleKey.forPlainTarget(new Label("//l:l2")),
-            RuleKey.forPlainTarget(new Label("//l:l1")));
+            TargetKey.forPlainTarget(new Label("//l:l2")),
+            TargetKey.forPlainTarget(new Label("//l:l1")));
     assertThat(reverseDependencies)
         .containsEntry(
-            RuleKey.forPlainTarget(new Label("//l:l3")),
-            RuleKey.forPlainTarget(new Label("//l:l1")));
+            TargetKey.forPlainTarget(new Label("//l:l3")),
+            TargetKey.forPlainTarget(new Label("//l:l1")));
   }
 
   @Test
   public void testTwoLabelsDepOnSameLabel() {
-    RuleMapBuilder builder = RuleMapBuilder.builder();
-    RuleMap ruleMap =
+    TargetMapBuilder builder = TargetMapBuilder.builder();
+    TargetMap targetMap =
         builder
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//l:l1")
                     .setKind("java_library")
                     .addDependency("//l:l3"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//l:l2")
                     .addDependency("//l:l3")
                     .setKind("java_library"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//l:l3")
                     .setKind("java_library"))
             .build();
 
-    ImmutableMultimap<RuleKey, RuleKey> reverseDependencies =
-        ReverseDependencyMap.createRdepsMap(ruleMap);
+    ImmutableMultimap<TargetKey, TargetKey> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(targetMap);
     assertThat(reverseDependencies)
         .containsEntry(
-            RuleKey.forPlainTarget(new Label("//l:l3")),
-            RuleKey.forPlainTarget(new Label("//l:l1")));
+            TargetKey.forPlainTarget(new Label("//l:l3")),
+            TargetKey.forPlainTarget(new Label("//l:l1")));
     assertThat(reverseDependencies)
         .containsEntry(
-            RuleKey.forPlainTarget(new Label("//l:l3")),
-            RuleKey.forPlainTarget(new Label("//l:l2")));
+            TargetKey.forPlainTarget(new Label("//l:l3")),
+            TargetKey.forPlainTarget(new Label("//l:l2")));
   }
 
   @Test
   public void testThreeLevelGraph() {
-    RuleMapBuilder builder = RuleMapBuilder.builder();
-    RuleMap ruleMap =
+    TargetMapBuilder builder = TargetMapBuilder.builder();
+    TargetMap targetMap =
         builder
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//l:l1")
                     .setKind("java_library")
                     .addDependency("//l:l3"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//l:l2")
                     .addDependency("//l:l3")
                     .setKind("java_library"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//l:l3")
                     .setKind("java_library"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//l:l4")
                     .addDependency("//l:l3")
                     .setKind("java_library"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("test/BUILD"))
                     .setLabel("//l:l5")
                     .addDependency("//l:l4")
                     .setKind("java_library"))
             .build();
 
-    ImmutableMultimap<RuleKey, RuleKey> reverseDependencies =
-        ReverseDependencyMap.createRdepsMap(ruleMap);
+    ImmutableMultimap<TargetKey, TargetKey> reverseDependencies =
+        ReverseDependencyMap.createRdepsMap(targetMap);
     assertThat(reverseDependencies)
         .containsEntry(
-            RuleKey.forPlainTarget(new Label("//l:l3")),
-            RuleKey.forPlainTarget(new Label("//l:l1")));
+            TargetKey.forPlainTarget(new Label("//l:l3")),
+            TargetKey.forPlainTarget(new Label("//l:l1")));
     assertThat(reverseDependencies)
         .containsEntry(
-            RuleKey.forPlainTarget(new Label("//l:l3")),
-            RuleKey.forPlainTarget(new Label("//l:l2")));
+            TargetKey.forPlainTarget(new Label("//l:l3")),
+            TargetKey.forPlainTarget(new Label("//l:l2")));
     assertThat(reverseDependencies)
         .containsEntry(
-            RuleKey.forPlainTarget(new Label("//l:l3")),
-            RuleKey.forPlainTarget(new Label("//l:l4")));
+            TargetKey.forPlainTarget(new Label("//l:l3")),
+            TargetKey.forPlainTarget(new Label("//l:l4")));
     assertThat(reverseDependencies)
         .containsEntry(
-            RuleKey.forPlainTarget(new Label("//l:l4")),
-            RuleKey.forPlainTarget(new Label("//l:l5")));
+            TargetKey.forPlainTarget(new Label("//l:l4")),
+            TargetKey.forPlainTarget(new Label("//l:l5")));
   }
 
   private static ArtifactLocation sourceRoot(String relativePath) {
diff --git a/base/tests/utils/integration/com/google/idea/blaze/base/BlazeIntegrationTestCase.java b/base/tests/utils/integration/com/google/idea/blaze/base/BlazeIntegrationTestCase.java
index 98dc27d..633c9df 100644
--- a/base/tests/utils/integration/com/google/idea/blaze/base/BlazeIntegrationTestCase.java
+++ b/base/tests/utils/integration/com/google/idea/blaze/base/BlazeIntegrationTestCase.java
@@ -15,14 +15,10 @@
  */
 package com.google.idea.blaze.base;
 
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableList;
 import com.google.idea.blaze.base.io.FileAttributeProvider;
 import com.google.idea.blaze.base.io.InputStreamProvider;
-import com.google.idea.blaze.base.lang.buildfile.search.FindUsages;
 import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
 import com.google.idea.blaze.base.settings.BlazeImportSettings;
@@ -33,53 +29,23 @@
 import com.google.idea.testing.EdtRule;
 import com.google.idea.testing.IntellijTestSetupRule;
 import com.google.idea.testing.ServiceHelper;
-import com.intellij.codeInsight.lookup.Lookup;
-import com.intellij.codeInsight.lookup.LookupElement;
-import com.intellij.codeInsight.lookup.LookupElementPresentation;
 import com.intellij.openapi.Disposable;
-import com.intellij.openapi.actionSystem.IdeActions;
 import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.application.ReadAction;
-import com.intellij.openapi.application.Result;
-import com.intellij.openapi.command.CommandProcessor;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.LogicalPosition;
 import com.intellij.openapi.extensions.ExtensionPointName;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.projectRoots.ProjectJdkTable;
-import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.openapi.vfs.ex.temp.TempFileSystem;
-import com.intellij.psi.PsiDirectory;
-import com.intellij.psi.PsiDocumentManager;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiManager;
-import com.intellij.psi.PsiNamedElement;
-import com.intellij.psi.PsiReference;
-import com.intellij.psi.impl.source.PostprocessReformattingAspect;
-import com.intellij.refactoring.move.moveClassesOrPackages.MoveDirectoryWithClassesProcessor;
-import com.intellij.testFramework.EditorTestUtil;
-import com.intellij.testFramework.EditorTestUtil.CaretAndSelectionState;
-import com.intellij.testFramework.EditorTestUtil.CaretInfo;
 import com.intellij.testFramework.EdtTestUtil;
 import com.intellij.testFramework.IdeaTestUtil;
-import com.intellij.testFramework.LightPlatformTestCase;
-import com.intellij.testFramework.LightProjectDescriptor;
 import com.intellij.testFramework.UsefulTestCase;
 import com.intellij.testFramework.fixtures.CodeInsightTestFixture;
 import com.intellij.testFramework.fixtures.IdeaProjectTestFixture;
 import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory;
 import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
-import com.intellij.testFramework.fixtures.TempDirTestFixture;
 import com.intellij.testFramework.fixtures.TestFixtureBuilder;
 import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl;
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 import org.junit.After;
 import org.junit.Before;
@@ -89,46 +55,56 @@
 /** Base test class for blaze integration tests. {@link UsefulTestCase} */
 public abstract class BlazeIntegrationTestCase {
 
-  private static final LightProjectDescriptor projectDescriptor =
-      LightCodeInsightFixtureTestCase.JAVA_8;
-
   @Rule public final IntellijTestSetupRule setupRule = new IntellijTestSetupRule();
   @Rule public final TestRule testRunWrapper = runTestsOnEdt() ? new EdtRule() : null;
 
   protected CodeInsightTestFixture testFixture;
   protected WorkspaceRoot workspaceRoot;
+  protected VirtualFile projectDataDirectory;
+  protected TestFileSystem fileSystem;
+  protected WorkspaceFileSystem workspace;
 
   @Before
   public final void setUp() throws Exception {
     IdeaTestFixtureFactory factory = IdeaTestFixtureFactory.getFixtureFactory();
     TestFixtureBuilder<IdeaProjectTestFixture> fixtureBuilder =
-        factory.createLightFixtureBuilder(projectDescriptor);
+        factory.createLightFixtureBuilder(LightCodeInsightFixtureTestCase.JAVA_8);
     final IdeaProjectTestFixture fixture = fixtureBuilder.getFixture();
-    testFixture = factory.createCodeInsightFixture(fixture, createTempDirFixture());
+    testFixture = factory.createCodeInsightFixture(fixture, new LightTempDirTestFixtureImpl(true));
     testFixture.setUp();
+    fileSystem = new TestFileSystem(getProject(), testFixture.getTempDirFixture());
 
     Runnable writeAction =
         () ->
             ApplicationManager.getApplication()
                 .runWriteAction(
-                    () -> ProjectJdkTable.getInstance().addJdk(IdeaTestUtil.getMockJdk18()));
+                    () -> {
+                      ProjectJdkTable.getInstance().addJdk(IdeaTestUtil.getMockJdk18());
+                      VirtualFile workspaceRootVirtualFile =
+                          fileSystem.createDirectory("workspace");
+                      workspaceRoot =
+                          new WorkspaceRoot(new File(workspaceRootVirtualFile.getPath()));
+                      projectDataDirectory = fileSystem.createDirectory("project-data-dir");
+                      workspace = new WorkspaceFileSystem(workspaceRoot, fileSystem);
+                    });
     EdtTestUtil.runInEdtAndWait(writeAction);
 
-    workspaceRoot = new WorkspaceRoot(new File(LightPlatformTestCase.getSourceRoot().getPath()));
-    setBlazeImportSettings(
-        new BlazeImportSettings(
-            workspaceRoot.toString(),
-            "test-project",
-            workspaceRoot + "/project-data-dir",
-            "location-hash",
-            workspaceRoot + "/project-view-file",
-            buildSystem()));
+    BlazeImportSettingsManager.getInstance(getProject())
+        .setImportSettings(
+            new BlazeImportSettings(
+                workspaceRoot.toString(),
+                "test-project",
+                projectDataDirectory.getPath(),
+                "location-hash",
+                workspaceRoot.fileForPath(new WorkspacePath("project-view-file")).getPath(),
+                buildSystem()));
 
-    registerApplicationService(FileAttributeProvider.class, new TempFileAttributeProvider());
+    registerApplicationService(
+        FileAttributeProvider.class, new TestFileSystem.TempFileAttributeProvider());
     registerApplicationService(
         InputStreamProvider.class,
         file -> {
-          VirtualFile vf = findFile(file.getPath());
+          VirtualFile vf = fileSystem.findFile(file.getPath());
           if (vf == null) {
             throw new FileNotFoundException();
           }
@@ -136,8 +112,10 @@
         });
   }
 
-  protected Disposable getTestRootDisposable() {
-    return setupRule.testRootDisposable;
+  @After
+  public final void tearDown() throws Exception {
+    testFixture.tearDown();
+    testFixture = null;
   }
 
   /** Override to run tests with bazel specified as the project's build system. */
@@ -150,250 +128,29 @@
     return true;
   }
 
-  @After
-  public final void tearDown() throws Exception {
-    testFixture.tearDown();
-    testFixture = null;
-  }
-
-  protected void setBlazeImportSettings(BlazeImportSettings importSettings) {
-    BlazeImportSettingsManager.getInstance(getProject()).setImportSettings(importSettings);
-  }
-
-  /** @return fixture to be used as temporary dir. */
-  protected TempDirTestFixture createTempDirFixture() {
-    return new LightTempDirTestFixtureImpl(true); // "tmp://src/" dir by default
-  }
-
-  /**
-   * Absolute file paths are prohibited -- the TempDirTestFixture used in these tests will prepend
-   * it's own root to the path.
-   */
-  protected void assertPathIsNotAbsolute(String filePath) {
-    assertThat(FileUtil.isAbsolute(filePath)).isFalse();
-  }
-
-  /** Creates a file with the specified contents and file path in the test project */
-  protected VirtualFile createFile(String filePath) {
-    return testFixture.getTempDirFixture().createFile(filePath);
-  }
-
-  /** Creates a file with the specified contents and file path in the test project */
-  protected VirtualFile createFile(String filePath, String... contentLines) {
-    return createFile(filePath, Joiner.on("\n").join(contentLines));
-  }
-
-  /** Creates a file with the specified contents and file path in the test project */
-  protected VirtualFile createFile(String filePath, String contents) {
-    assertPathIsNotAbsolute(filePath);
-    try {
-      return testFixture.getTempDirFixture().createFile(filePath, contents);
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-  protected PsiDirectory createPsiDirectory(String path) {
-    return getPsiDirectory(createDirectory(path));
-  }
-
-  protected VirtualFile createDirectory(String path) {
-    assertPathIsNotAbsolute(path);
-    try {
-      return testFixture.getTempDirFixture().findOrCreateDir(path);
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-  protected Editor openFileInEditor(PsiFile file) {
-    return openFileInEditor(file.getVirtualFile());
-  }
-
-  protected Editor openFileInEditor(VirtualFile file) {
-    EdtTestUtil.runInEdtAndWait((Runnable) () -> testFixture.openFileInEditor(file));
-    return testFixture.getEditor();
-  }
-
-  /** @return null if the only item was auto-completed */
-  @Nullable
-  protected String[] getCompletionItemsAsStrings() {
-    LookupElement[] completionItems = testFixture.completeBasic();
-    if (completionItems == null) {
-      return null;
-    }
-    return Arrays.stream(completionItems)
-        .map(LookupElement::getLookupString)
-        .toArray(String[]::new);
-  }
-
-  /** @return null if the only item was auto-completed */
-  @Nullable
-  protected String[] getCompletionItemsAsSuggestionStrings() {
-    LookupElement[] completionItems = testFixture.completeBasic();
-    if (completionItems == null) {
-      return null;
-    }
-    LookupElementPresentation presentation = new LookupElementPresentation();
-    String[] strings = new String[completionItems.length];
-    for (int i = 0; i < strings.length; i++) {
-      completionItems[i].renderElement(presentation);
-      strings[i] = presentation.getItemText();
-    }
-    return strings;
-  }
-
-  /** @return true if a LookupItem was inserted. */
-  protected boolean completeIfUnique() {
-    LookupElement[] completionItems = testFixture.completeBasic();
-    if (completionItems == null) {
-      return true;
-    }
-    if (completionItems.length != 1) {
-      return false;
-    }
-    testFixture.getLookup().setCurrentItem(completionItems[0]);
-    testFixture.finishLookup(Lookup.NORMAL_SELECT_CHAR);
-    return true;
-  }
-
-  /** Simulates a user typing action, at current caret position of file. */
-  protected void performTypingAction(PsiFile file, char typedChar) {
-    performTypingAction(openFileInEditor(file.getVirtualFile()), typedChar);
-  }
-
-  /** Simulates a user typing action, at current caret position of document. */
-  protected void performTypingAction(Editor editor, char typedChar) {
-    EditorTestUtil.performTypingAction(editor, typedChar);
-    getProject().getComponent(PostprocessReformattingAspect.class).doPostponedFormatting();
-    PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
-  }
-
-  /**
-   * Clicks the specified button in current document at the current caret position
-   *
-   * @param action which button to click (see {@link IdeActions})
-   */
-  protected final void pressButton(final String action) {
-    CommandProcessor.getInstance()
-        .executeCommand(getProject(), () -> testFixture.performEditorAction(action), "", null);
-  }
-
-  protected void setCaretPosition(Editor editor, int lineNumber, int columnNumber) {
-    final CaretInfo info = new CaretInfo(new LogicalPosition(lineNumber, columnNumber), null);
-    EdtTestUtil.runInEdtAndWait(
-        (Runnable)
-            () ->
-                EditorTestUtil.setCaretsAndSelection(
-                    editor, new CaretAndSelectionState(ImmutableList.of(info), null)));
-  }
-
-  protected void assertCaretPosition(Editor editor, int lineNumber, int columnNumber) {
-    CaretInfo info = new CaretInfo(new LogicalPosition(lineNumber, columnNumber), null);
-    EditorTestUtil.verifyCaretAndSelectionState(
-        editor, new CaretAndSelectionState(ImmutableList.of(info), null));
-  }
-
   protected Project getProject() {
     return testFixture.getProject();
   }
 
-  protected VirtualFile findFile(String filePath) {
-    VirtualFile vf = TempFileSystem.getInstance().findFileByPath(filePath);
-    if (vf == null) {
-      // this might be a relative path
-      vf = testFixture.getTempDirFixture().getFile(filePath);
-    }
-    return vf;
+  protected Disposable getTestRootDisposable() {
+    return setupRule.testRootDisposable;
   }
 
-  protected void assertFileContents(String filePath, String... contentLines) {
-    assertFileContents(findFile(filePath), contentLines);
+  protected <T> void registerApplicationService(Class<T> key, T implementation) {
+    ServiceHelper.registerApplicationService(key, implementation, getTestRootDisposable());
   }
 
-  protected void assertFileContents(VirtualFile file, String... contentLines) {
-    assertFileContents(getPsiFile(file), contentLines);
+  protected <T> void registerApplicationComponent(Class<T> key, T implementation) {
+    ServiceHelper.registerApplicationComponent(key, implementation, getTestRootDisposable());
   }
 
-  protected void assertFileContents(PsiFile file, String... contentLines) {
-    String contents = Joiner.on("\n").join(contentLines);
-    assertThat(file.getText()).isEqualTo(contents);
+  protected <T> void registerProjectService(Class<T> key, T implementation) {
+    ServiceHelper.registerProjectService(
+        getProject(), key, implementation, getTestRootDisposable());
   }
 
-  /** Creates a file with the specified contents and file path in the test project */
-  protected PsiFile createPsiFile(String filePath) {
-    return getPsiFile(testFixture.getTempDirFixture().createFile(filePath));
-  }
-
-  /** Creates a file with the specified contents and file path in the test project */
-  protected PsiFile createPsiFile(String filePath, String... contentLines) {
-    return getPsiFile(createFile(filePath, contentLines));
-  }
-
-  /** Finds PsiFile, and asserts that it's not null. */
-  protected PsiFile getPsiFile(VirtualFile file) {
-    return new ReadAction<PsiFile>() {
-      @Override
-      protected void run(Result<PsiFile> result) throws Throwable {
-        PsiFile psiFile = PsiManager.getInstance(getProject()).findFile(file);
-        assertThat(psiFile).isNotNull();
-        result.setResult(psiFile);
-      }
-    }.execute().getResultObject();
-  }
-
-  /** Finds PsiDirectory, and asserts that it's not null. */
-  protected PsiDirectory getPsiDirectory(VirtualFile file) {
-    return new ReadAction<PsiDirectory>() {
-      @Override
-      protected void run(Result<PsiDirectory> result) throws Throwable {
-        PsiDirectory psiFile = PsiManager.getInstance(getProject()).findDirectory(file);
-        assertThat(psiFile).isNotNull();
-        result.setResult(psiFile);
-      }
-    }.execute().getResultObject();
-  }
-
-  protected PsiDirectory renameDirectory(String oldPath, String newPath) {
-    try {
-      VirtualFile original = findFile(oldPath);
-      PsiDirectory originalPsi = PsiManager.getInstance(getProject()).findDirectory(original);
-      assertThat(originalPsi).isNotNull();
-
-      VirtualFile destination = testFixture.getTempDirFixture().findOrCreateDir(newPath);
-      PsiDirectory destPsi = PsiManager.getInstance(getProject()).findDirectory(destination);
-      assertThat(destPsi).isNotNull();
-
-      new MoveDirectoryWithClassesProcessor(
-              getProject(), new PsiDirectory[] {originalPsi}, destPsi, true, true, false, null)
-          .run();
-      return destPsi;
-
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-  protected void renamePsiElement(PsiNamedElement element, String newName) {
-    testFixture.renameElement(element, newName);
-  }
-
-  protected void handleRename(PsiReference reference, String newName) {
-    doRenameOperation(() -> reference.handleElementRename(newName));
-  }
-
-  protected void doRenameOperation(Runnable renameOp) {
-    ApplicationManager.getApplication()
-        .runWriteAction(() -> CommandProcessor.getInstance().runUndoTransparentAction(renameOp));
-  }
-
-  protected static <T> List<T> findAllReferencingElementsOfType(
-      PsiElement target, Class<T> referenceType) {
-    return Arrays.stream(FindUsages.findAllReferences(target))
-        .map(PsiReference::getElement)
-        .filter(referenceType::isInstance)
-        .map(e -> (T) e)
-        .collect(Collectors.toList());
+  protected <T> void registerExtension(ExtensionPointName<T> name, T instance) {
+    ServiceHelper.registerExtension(name, instance, getTestRootDisposable());
   }
 
   protected void mockBlazeProjectDataManager(BlazeProjectData data) {
@@ -415,45 +172,4 @@
         };
     registerProjectService(BlazeProjectDataManager.class, mockProjectDataManager);
   }
-
-  protected <T> void registerApplicationService(Class<T> key, T implementation) {
-    ServiceHelper.registerApplicationService(key, implementation, getTestRootDisposable());
-  }
-
-  protected <T> void registerProjectService(Class<T> key, T implementation) {
-    ServiceHelper.registerProjectService(
-        getProject(), key, implementation, getTestRootDisposable());
-  }
-
-  protected <T> void registerExtension(ExtensionPointName<T> name, T instance) {
-    ServiceHelper.registerExtension(name, instance, getTestRootDisposable());
-  }
-
-  /** Redirects file system checks via the TempFileSystem used for these tests. */
-  private static class TempFileAttributeProvider extends FileAttributeProvider {
-
-    final TempFileSystem fileSystem = TempFileSystem.getInstance();
-
-    @Override
-    public boolean exists(File file) {
-      VirtualFile vf = getVirtualFile(file);
-      return vf != null && vf.exists();
-    }
-
-    @Override
-    public boolean isDirectory(File file) {
-      VirtualFile vf = getVirtualFile(file);
-      return vf != null && vf.isDirectory();
-    }
-
-    @Override
-    public boolean isFile(File file) {
-      VirtualFile vf = getVirtualFile(file);
-      return vf != null && vf.exists() && !vf.isDirectory();
-    }
-
-    private VirtualFile getVirtualFile(File file) {
-      return fileSystem.findFileByPath(file.getPath());
-    }
-  }
 }
diff --git a/base/tests/utils/integration/com/google/idea/blaze/base/EditorTestHelper.java b/base/tests/utils/integration/com/google/idea/blaze/base/EditorTestHelper.java
new file mode 100644
index 0000000..e7a2d4e
--- /dev/null
+++ b/base/tests/utils/integration/com/google/idea/blaze/base/EditorTestHelper.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
+import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.intellij.codeInsight.lookup.Lookup;
+import com.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.codeInsight.lookup.LookupElementPresentation;
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.LogicalPosition;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.impl.source.PostprocessReformattingAspect;
+import com.intellij.testFramework.EditorTestUtil;
+import com.intellij.testFramework.EditorTestUtil.CaretAndSelectionState;
+import com.intellij.testFramework.EditorTestUtil.CaretInfo;
+import com.intellij.testFramework.EdtTestUtil;
+import com.intellij.testFramework.fixtures.CodeInsightTestFixture;
+import java.util.Arrays;
+import javax.annotation.Nullable;
+
+/** Helper methods for editor tests. */
+public class EditorTestHelper {
+  private final Project project;
+  private final CodeInsightTestFixture testFixture;
+
+  public EditorTestHelper(Project project, CodeInsightTestFixture testFixture) {
+    this.project = project;
+    this.testFixture = testFixture;
+  }
+
+  public Editor openFileInEditor(PsiFile file) {
+    return openFileInEditor(file.getVirtualFile());
+  }
+
+  public Editor openFileInEditor(VirtualFile file) {
+    EdtTestUtil.runInEdtAndWait((Runnable) () -> testFixture.openFileInEditor(file));
+    return testFixture.getEditor();
+  }
+
+  /** @return null if the only item was auto-completed */
+  @Nullable
+  public String[] getCompletionItemsAsStrings() {
+    LookupElement[] completionItems = testFixture.completeBasic();
+    if (completionItems == null) {
+      return null;
+    }
+    return Arrays.stream(completionItems)
+        .map(LookupElement::getLookupString)
+        .toArray(String[]::new);
+  }
+
+  /** @return null if the only item was auto-completed */
+  @Nullable
+  public String[] getCompletionItemsAsSuggestionStrings() {
+    LookupElement[] completionItems = testFixture.completeBasic();
+    if (completionItems == null) {
+      return null;
+    }
+    LookupElementPresentation presentation = new LookupElementPresentation();
+    String[] strings = new String[completionItems.length];
+    for (int i = 0; i < strings.length; i++) {
+      completionItems[i].renderElement(presentation);
+      strings[i] = presentation.getItemText();
+    }
+    return strings;
+  }
+
+  /** @return true if a LookupItem was inserted. */
+  public boolean completeIfUnique() {
+    LookupElement[] completionItems = testFixture.completeBasic();
+    if (completionItems == null) {
+      return true;
+    }
+    if (completionItems.length != 1) {
+      return false;
+    }
+    testFixture.getLookup().setCurrentItem(completionItems[0]);
+    testFixture.finishLookup(Lookup.NORMAL_SELECT_CHAR);
+    return true;
+  }
+
+  /** Simulates a user typing action, at current caret position of file. */
+  public void performTypingAction(PsiFile file, char typedChar) {
+    performTypingAction(openFileInEditor(file.getVirtualFile()), typedChar);
+  }
+
+  /** Simulates a user typing action, at current caret position of document. */
+  public void performTypingAction(Editor editor, char typedChar) {
+    EditorTestUtil.performTypingAction(editor, typedChar);
+    project.getComponent(PostprocessReformattingAspect.class).doPostponedFormatting();
+    PsiDocumentManager.getInstance(project).commitAllDocuments();
+  }
+
+  /**
+   * Clicks the specified button in current document at the current caret position
+   *
+   * @param action which button to click (see {@link IdeActions})
+   */
+  public final void pressButton(final String action) {
+    CommandProcessor.getInstance()
+        .executeCommand(project, () -> testFixture.performEditorAction(action), "", null);
+  }
+
+  public void setCaretPosition(Editor editor, int lineNumber, int columnNumber) {
+    final CaretInfo info = new CaretInfo(new LogicalPosition(lineNumber, columnNumber), null);
+    EdtTestUtil.runInEdtAndWait(
+        (Runnable)
+            () ->
+                EditorTestUtil.setCaretsAndSelection(
+                    editor, new CaretAndSelectionState(ImmutableList.of(info), null)));
+  }
+
+  public void assertCaretPosition(Editor editor, int lineNumber, int columnNumber) {
+    CaretInfo info = new CaretInfo(new LogicalPosition(lineNumber, columnNumber), null);
+    EditorTestUtil.verifyCaretAndSelectionState(
+        editor, new CaretAndSelectionState(ImmutableList.of(info), null));
+  }
+
+  public void replaceStringContents(StringLiteral string, String newStringContents) {
+    Runnable renameOp =
+        () -> {
+          ASTNode node = string.getNode();
+          node.replaceChild(
+              node.getFirstChildNode(),
+              PsiUtils.createNewLabel(string.getProject(), newStringContents));
+        };
+    ApplicationManager.getApplication()
+        .runWriteAction(() -> CommandProcessor.getInstance().runUndoTransparentAction(renameOp));
+  }
+}
diff --git a/base/tests/utils/integration/com/google/idea/blaze/base/TestFileSystem.java b/base/tests/utils/integration/com/google/idea/blaze/base/TestFileSystem.java
new file mode 100644
index 0000000..834eeab
--- /dev/null
+++ b/base/tests/utils/integration/com/google/idea/blaze/base/TestFileSystem.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+import com.google.idea.blaze.base.io.FileAttributeProvider;
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.ex.temp.TempFileSystem;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.testFramework.LightPlatformTestCase;
+import com.intellij.testFramework.fixtures.TempDirTestFixture;
+import java.io.File;
+import java.io.IOException;
+
+/** Creates temp files for integration tests. */
+public class TestFileSystem {
+  private final Project project;
+  private final TempDirTestFixture tempDirTestFixture;
+
+  public TestFileSystem(Project project, TempDirTestFixture tempDirTestFixture) {
+    this.project = project;
+    this.tempDirTestFixture = tempDirTestFixture;
+  }
+
+  /** Creates an empty file in the temp file system */
+  public VirtualFile createFile(String filePath) {
+    filePath = makePathRelativeToTestFixture(filePath);
+    return tempDirTestFixture.createFile(filePath);
+  }
+
+  /** Creates a file with the specified contents in the temp file system */
+  public VirtualFile createFile(String filePath, String... contentLines) {
+    return createFile(filePath, Joiner.on("\n").join(contentLines));
+  }
+
+  /** Creates a file with the specified contents in the temp file system */
+  public VirtualFile createFile(String filePath, String contents) {
+    filePath = makePathRelativeToTestFixture(filePath);
+    try {
+      return tempDirTestFixture.createFile(filePath, contents);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /** Creates a directory in the temp file system */
+  public VirtualFile createDirectory(String path) {
+    path = makePathRelativeToTestFixture(path);
+    try {
+      return tempDirTestFixture.findOrCreateDir(path);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /** Creates a psi directory in the temp file system */
+  public PsiDirectory createPsiDirectory(String path) {
+    return getPsiDirectory(createDirectory(path));
+  }
+
+  /** Creates a psi file in the temp file system */
+  public PsiFile createPsiFile(String filePath) {
+    filePath = makePathRelativeToTestFixture(filePath);
+    return getPsiFile(tempDirTestFixture.createFile(filePath));
+  }
+
+  /** Creates a psi file with the specified contents and file path in the temp file system */
+  public PsiFile createPsiFile(String filePath, String... contentLines) {
+    return getPsiFile(createFile(filePath, contentLines));
+  }
+
+  /** Finds PsiFile, and asserts that it's not null. */
+  public PsiFile getPsiFile(VirtualFile file) {
+    return new ReadAction<PsiFile>() {
+      @Override
+      protected void run(Result<PsiFile> result) throws Throwable {
+        PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
+        assertThat(psiFile).isNotNull();
+        result.setResult(psiFile);
+      }
+    }.execute().getResultObject();
+  }
+
+  /** Finds PsiDirectory, and asserts that it's not null. */
+  public PsiDirectory getPsiDirectory(VirtualFile file) {
+    return new ReadAction<PsiDirectory>() {
+      @Override
+      protected void run(Result<PsiDirectory> result) throws Throwable {
+        PsiDirectory psiFile = PsiManager.getInstance(project).findDirectory(file);
+        assertThat(psiFile).isNotNull();
+        result.setResult(psiFile);
+      }
+    }.execute().getResultObject();
+  }
+
+  public VirtualFile findFile(String filePath) {
+    VirtualFile vf = TempFileSystem.getInstance().findFileByPath(filePath);
+    if (vf == null) {
+      // this might be a relative path
+      filePath = makePathRelativeToTestFixture(filePath);
+      vf = tempDirTestFixture.getFile(filePath);
+    }
+    return vf;
+  }
+
+  public VirtualFile findOrCreateDirectory(String path) throws IOException {
+    path = makePathRelativeToTestFixture(path);
+    return tempDirTestFixture.findOrCreateDir(path);
+  }
+
+  /**
+   * Absolute file paths are prohibited -- the TempDirTestFixture used in these tests will prepend
+   * it's own root to the path.
+   */
+  private String makePathRelativeToTestFixture(String filePath) {
+    if (!FileUtil.isAbsolute(filePath)) {
+      return filePath;
+    }
+    String tempDirPath = LightPlatformTestCase.getSourceRoot().getPath();
+    assertThat(FileUtil.isAncestor(tempDirPath, filePath, true)).isTrue();
+    return FileUtil.getRelativePath(tempDirPath, filePath, File.separatorChar);
+  }
+
+  /** Redirects file system checks via the TempFileSystem used for these tests. */
+  public static class TempFileAttributeProvider extends FileAttributeProvider {
+
+    final TempFileSystem fileSystem = TempFileSystem.getInstance();
+
+    @Override
+    public boolean exists(File file) {
+      VirtualFile vf = getVirtualFile(file);
+      return vf != null && vf.exists();
+    }
+
+    @Override
+    public boolean isDirectory(File file) {
+      VirtualFile vf = getVirtualFile(file);
+      return vf != null && vf.isDirectory();
+    }
+
+    @Override
+    public boolean isFile(File file) {
+      VirtualFile vf = getVirtualFile(file);
+      return vf != null && vf.exists() && !vf.isDirectory();
+    }
+
+    private VirtualFile getVirtualFile(File file) {
+      return fileSystem.findFileByPath(file.getPath());
+    }
+  }
+}
diff --git a/base/tests/utils/integration/com/google/idea/blaze/base/WorkspaceFileSystem.java b/base/tests/utils/integration/com/google/idea/blaze/base/WorkspaceFileSystem.java
new file mode 100644
index 0000000..7f66076
--- /dev/null
+++ b/base/tests/utils/integration/com/google/idea/blaze/base/WorkspaceFileSystem.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base;
+
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
+import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+
+/** Creates test files in the workspace */
+public class WorkspaceFileSystem {
+  private final WorkspaceRoot workspaceRoot;
+  private final TestFileSystem testFileSystem;
+
+  public WorkspaceFileSystem(WorkspaceRoot workspaceRoot, TestFileSystem testFileSystem) {
+    this.workspaceRoot = workspaceRoot;
+    this.testFileSystem = testFileSystem;
+  }
+
+  /** Creates an empty file in the workspace */
+  public VirtualFile createFile(WorkspacePath workspacePath) {
+    return testFileSystem.createFile(workspaceRoot.fileForPath(workspacePath).getPath());
+  }
+
+  /** Creates a file with the specified contents in the workspace */
+  public VirtualFile createFile(WorkspacePath workspacePath, String... contentLines) {
+    return testFileSystem.createFile(
+        workspaceRoot.fileForPath(workspacePath).getPath(), contentLines);
+  }
+
+  /** Creates a file with the specified contents in the workspace */
+  public VirtualFile createFile(WorkspacePath workspacePath, String contents) {
+    return testFileSystem.createFile(workspaceRoot.fileForPath(workspacePath).getPath(), contents);
+  }
+
+  /** Creates a directory in the workspace */
+  public VirtualFile createDirectory(WorkspacePath workspacePath) {
+    return testFileSystem.createDirectory(workspaceRoot.fileForPath(workspacePath).getPath());
+  }
+
+  /** Creates an empty psi file in the workspace */
+  public PsiFile createPsiFile(WorkspacePath workspacePath) {
+    return testFileSystem.createPsiFile(workspaceRoot.fileForPath(workspacePath).getPath());
+  }
+
+  /** Creates a psi file with the specified contents in the workspace */
+  public PsiFile createPsiFile(WorkspacePath workspacePath, String... contentLines) {
+    return testFileSystem.createPsiFile(
+        workspaceRoot.fileForPath(workspacePath).getPath(), contentLines);
+  }
+
+  /** Creates a psi directory in the workspace */
+  public PsiDirectory createPsiDirectory(WorkspacePath workspacePath) {
+    return testFileSystem.getPsiDirectory(createDirectory(workspacePath));
+  }
+}
diff --git a/base/tests/utils/integration/com/google/idea/blaze/base/lang/buildfile/BuildFileIntegrationTestCase.java b/base/tests/utils/integration/com/google/idea/blaze/base/lang/buildfile/BuildFileIntegrationTestCase.java
index 0dcea85..efa81a8 100644
--- a/base/tests/utils/integration/com/google/idea/blaze/base/lang/buildfile/BuildFileIntegrationTestCase.java
+++ b/base/tests/utils/integration/com/google/idea/blaze/base/lang/buildfile/BuildFileIntegrationTestCase.java
@@ -17,51 +17,53 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.idea.blaze.base.BlazeIntegrationTestCase;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
+import com.google.idea.blaze.base.EditorTestHelper;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
-import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
-import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoderImpl;
 import com.google.idea.blaze.base.sync.workspace.BlazeRoots;
 import com.google.idea.blaze.base.sync.workspace.WorkingSet;
 import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
 import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolverImpl;
-import com.intellij.lang.ASTNode;
+import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiFile;
 import org.junit.Before;
 
 /** BUILD file specific integration test base */
 public abstract class BuildFileIntegrationTestCase extends BlazeIntegrationTestCase {
+  protected EditorTestHelper editorTest;
 
   @Before
   public final void doSetup() {
     mockBlazeProjectDataManager(getMockBlazeProjectData());
+    editorTest = new EditorTestHelper(getProject(), testFixture);
   }
 
   /**
    * Creates a file with the specified contents and file path in the test project, and asserts that
    * it's parsed as a BuildFile
    */
-  protected BuildFile createBuildFile(String filePath, String... contentLines) {
-    PsiFile file = createPsiFile(filePath, contentLines);
+  protected BuildFile createBuildFile(WorkspacePath workspacePath, String... contentLines) {
+    PsiFile file = workspace.createPsiFile(workspacePath, contentLines);
     assertThat(file).isInstanceOf(BuildFile.class);
     return (BuildFile) file;
   }
 
-  protected void replaceStringContents(StringLiteral string, String newStringContents) {
-    doRenameOperation(
-        () -> {
-          ASTNode node = string.getNode();
-          node.replaceChild(
-              node.getFirstChildNode(),
-              PsiUtils.createNewLabel(string.getProject(), newStringContents));
-        });
+  protected void assertFileContents(VirtualFile file, String... contentLines) {
+    assertFileContents(fileSystem.getPsiFile(file), contentLines);
+  }
+
+  protected void assertFileContents(PsiFile file, String... contentLines) {
+    String contents = Joiner.on("\n").join(contentLines);
+    assertThat(file.getText()).isEqualTo(contents);
   }
 
   private BlazeProjectData getMockBlazeProjectData() {
@@ -70,14 +72,16 @@
             null,
             ImmutableList.of(workspaceRoot.directory()),
             new ExecutionRootPath("out/crosstool/bin"),
-            new ExecutionRootPath("out/crosstool/gen"));
+            new ExecutionRootPath("out/crosstool/gen"),
+            null);
     WorkspacePathResolver workspacePathResolver =
         new WorkspacePathResolverImpl(workspaceRoot, fakeRoots);
     ArtifactLocationDecoder artifactLocationDecoder =
         new ArtifactLocationDecoderImpl(fakeRoots, workspacePathResolver);
     return new BlazeProjectData(
         0,
-        new RuleMap(ImmutableMap.of()),
+        new TargetMap(ImmutableMap.of()),
+        ImmutableMap.of(),
         fakeRoots,
         new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()),
         workspacePathResolver,
diff --git a/base/tests/utils/integration/com/google/idea/blaze/base/sync/BlazeSyncIntegrationTestCase.java b/base/tests/utils/integration/com/google/idea/blaze/base/sync/BlazeSyncIntegrationTestCase.java
index fd78f8c..1424a0d 100644
--- a/base/tests/utils/integration/com/google/idea/blaze/base/sync/BlazeSyncIntegrationTestCase.java
+++ b/base/tests/utils/integration/com/google/idea/blaze/base/sync/BlazeSyncIntegrationTestCase.java
@@ -28,8 +28,7 @@
 import com.google.idea.blaze.base.BlazeIntegrationTestCase;
 import com.google.idea.blaze.base.command.info.BlazeInfo;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
-import com.google.idea.blaze.base.io.WorkspaceScanner;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import com.google.idea.blaze.base.model.SyncState;
 import com.google.idea.blaze.base.model.primitives.TargetExpression;
 import com.google.idea.blaze.base.model.primitives.WorkspacePath;
@@ -56,33 +55,22 @@
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.roots.ModifiableRootModel;
 import com.intellij.openapi.util.Disposer;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.testFramework.fixtures.TempDirTestFixture;
-import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl;
-import java.io.File;
-import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 import javax.annotation.Nullable;
-import org.junit.After;
 import org.junit.Before;
 
 /** Sets up mocks required for integration tests of the blaze sync process. */
 public abstract class BlazeSyncIntegrationTestCase extends BlazeIntegrationTestCase {
 
-  // root directory for all files outside the project directory.
-  protected TempDirTestFixture tempDirectoryHandler;
-  protected VirtualFile tempDirectory;
-
   // blaze-info data
+  private static final String OUTPUT_BASE = "/output_base";
   private static final String EXECUTION_ROOT = "/execroot/root";
   private static final String BLAZE_BIN =
       EXECUTION_ROOT + "/blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-fastbuild/bin";
   private static final String BLAZE_GENFILES =
       EXECUTION_ROOT + "/blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-fastbuild/genfiles";
 
-  private static final String PROJECT_DATA_DIR = "project-data-dir";
-
   private MockProjectViewManager projectViewManager;
   private MockBlazeVcsHandler vcsHandler;
   private MockBlazeInfo blazeInfoData;
@@ -93,27 +81,12 @@
 
   @Before
   public void doSetup() throws Exception {
-    // Set up a workspace root outside of the tracked temp file system.
-    tempDirectoryHandler = new LightTempDirTestFixtureImpl();
-    tempDirectoryHandler.setUp();
-    tempDirectory = tempDirectoryHandler.getFile("");
-    workspaceRoot = new WorkspaceRoot(new File(tempDirectory.getPath()));
-    setBlazeImportSettings(
-        new BlazeImportSettings(
-            workspaceRoot.toString(),
-            "test-project",
-            workspaceRoot + "/" + PROJECT_DATA_DIR,
-            "location-hash",
-            workspaceRoot + "/project-view-file",
-            BuildSystem.Blaze));
-
     projectViewManager = new MockProjectViewManager();
     vcsHandler = new MockBlazeVcsHandler();
     blazeInfoData = new MockBlazeInfo();
     blazeIdeInterface = new MockBlazeIdeInterface();
     registerProjectService(ProjectViewManager.class, projectViewManager);
     registerExtension(BlazeVcsHandler.EP_NAME, vcsHandler);
-    registerApplicationService(WorkspaceScanner.class, (workspaceRoot, workspacePath) -> true);
     registerApplicationService(BlazeInfo.class, blazeInfoData);
     registerApplicationService(BlazeIdeInterface.class, blazeIdeInterface);
     registerApplicationService(
@@ -139,9 +112,9 @@
     context = new BlazeContext();
     context.addOutputSink(IssueOutput.class, errorCollector);
 
-    tempDirectoryHandler.findOrCreateDir(PROJECT_DATA_DIR + "/.blaze/modules");
+    fileSystem.createDirectory(projectDataDirectory.getPath() + "/.blaze/modules");
 
-    setBlazeInfoResults(
+    blazeInfoData.setResults(
         ImmutableMap.of(
             BlazeInfo.blazeBinKey(Blaze.getBuildSystem(getProject())),
             BLAZE_BIN,
@@ -149,35 +122,14 @@
             BLAZE_GENFILES,
             BlazeInfo.EXECUTION_ROOT_KEY,
             EXECUTION_ROOT,
+            BlazeInfo.OUTPUT_BASE_KEY,
+            OUTPUT_BASE,
             BlazeInfo.PACKAGE_PATH_KEY,
             workspaceRoot.toString()));
   }
 
-  @After
-  public final void doTearDown() throws Exception {
-    if (tempDirectoryHandler != null) {
-      tempDirectoryHandler.tearDown();
-    }
-  }
-
-  protected VirtualFile createWorkspaceFile(String relativePath, @Nullable String... contents) {
-    try {
-      String content = contents != null ? Joiner.on("\n").join(contents) : "";
-      return tempDirectoryHandler.createFile(relativePath, content);
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-  protected void assertNoErrors() {
-    errorCollector.assertNoIssues();
-  }
-
-  protected ArtifactLocation sourceRoot(String relativePath) {
-    return ArtifactLocation.builder()
-        .setRelativePath(relativePath)
-        .setIsSource(true)
-        .build();
+  protected static ArtifactLocation sourceRoot(String relativePath) {
+    return ArtifactLocation.builder().setRelativePath(relativePath).setIsSource(true).build();
   }
 
   protected void setProjectView(String... contents) {
@@ -187,7 +139,7 @@
 
     ProjectViewSet result = projectViewParser.getResult();
     assertThat(result.getProjectViewFiles()).isNotEmpty();
-    assertNoErrors();
+    errorCollector.assertNoIssues();
     setProjectViewSet(result);
   }
 
@@ -195,12 +147,8 @@
     projectViewManager.projectViewSet = projectViewSet;
   }
 
-  protected void setRuleMap(RuleMap ruleMap) {
-    blazeIdeInterface.ruleMap = ruleMap;
-  }
-
-  protected void setBlazeInfoResults(Map<String, String> blazeInfoResults) {
-    blazeInfoData.setResults(blazeInfoResults);
+  protected void setTargetMap(TargetMap targetMap) {
+    blazeIdeInterface.targetMap = targetMap;
   }
 
   protected void runBlazeSync(BlazeSyncParams syncParams) {
@@ -302,10 +250,10 @@
   }
 
   private static class MockBlazeIdeInterface implements BlazeIdeInterface {
-    private RuleMap ruleMap = new RuleMap(ImmutableMap.of());
+    private TargetMap targetMap = new TargetMap(ImmutableMap.of());
 
     @Override
-    public IdeResult updateRuleMap(
+    public IdeResult updateTargetMap(
         Project project,
         BlazeContext context,
         WorkspaceRoot workspaceRoot,
@@ -316,7 +264,7 @@
         SyncState.Builder syncStateBuilder,
         @Nullable SyncState previousSyncState,
         boolean mergeWithOldState) {
-      return new IdeResult(ruleMap, BuildResult.SUCCESS);
+      return new IdeResult(targetMap, BuildResult.SUCCESS);
     }
 
     @Override
diff --git a/base/tests/utils/unit/com/google/idea/blaze/base/ideinfo/RuleMapBuilder.java b/base/tests/utils/unit/com/google/idea/blaze/base/ideinfo/RuleMapBuilder.java
deleted file mode 100644
index 2b82cd9..0000000
--- a/base/tests/utils/unit/com/google/idea/blaze/base/ideinfo/RuleMapBuilder.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2016 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.idea.blaze.base.ideinfo;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import java.util.List;
-import org.jetbrains.annotations.NotNull;
-
-/** Builds a rule map. */
-public class RuleMapBuilder {
-  private List<RuleIdeInfo> rules = Lists.newArrayList();
-
-  public static RuleMapBuilder builder() {
-    return new RuleMapBuilder();
-  }
-
-  @NotNull
-  public RuleMapBuilder addRule(@NotNull RuleIdeInfo ruleOrLibrary) {
-    rules.add(ruleOrLibrary);
-    return this;
-  }
-
-  @NotNull
-  public RuleMapBuilder addRule(@NotNull RuleIdeInfo.Builder ruleOrLibrary) {
-    return addRule(ruleOrLibrary.build());
-  }
-
-  @NotNull
-  public RuleMap build() {
-    ImmutableMap.Builder<RuleKey, RuleIdeInfo> ruleMap = ImmutableMap.builder();
-    for (RuleIdeInfo rule : rules) {
-      RuleKey key = rule.key;
-      ruleMap.put(key, rule);
-    }
-    return new RuleMap(ruleMap.build());
-  }
-}
diff --git a/base/tests/utils/unit/com/google/idea/blaze/base/ideinfo/TargetMapBuilder.java b/base/tests/utils/unit/com/google/idea/blaze/base/ideinfo/TargetMapBuilder.java
new file mode 100644
index 0000000..c29e136
--- /dev/null
+++ b/base/tests/utils/unit/com/google/idea/blaze/base/ideinfo/TargetMapBuilder.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.base.ideinfo;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import java.util.List;
+
+/** Builds a target map. */
+public class TargetMapBuilder {
+  private List<TargetIdeInfo> targets = Lists.newArrayList();
+
+  public static TargetMapBuilder builder() {
+    return new TargetMapBuilder();
+  }
+
+  public TargetMapBuilder addTarget(TargetIdeInfo target) {
+    targets.add(target);
+    return this;
+  }
+
+  public TargetMapBuilder addTarget(TargetIdeInfo.Builder target) {
+    return addTarget(target.build());
+  }
+
+  public TargetMap build() {
+    ImmutableMap.Builder<TargetKey, TargetIdeInfo> targetMap = ImmutableMap.builder();
+    for (TargetIdeInfo target : targets) {
+      TargetKey key = target.key;
+      targetMap.put(key, target);
+    }
+    return new TargetMap(targetMap.build());
+  }
+}
diff --git a/clwb/src/META-INF/clwb.xml b/clwb/src/META-INF/clwb.xml
index b6d8dbf..222ef38 100644
--- a/clwb/src/META-INF/clwb.xml
+++ b/clwb/src/META-INF/clwb.xml
@@ -23,9 +23,9 @@
                         serviceImplementation="com.google.idea.blaze.plugin.ClwbPluginId"/>
     <projectService serviceInterface="com.google.idea.blaze.base.ui.BlazeProblemsView"
                     serviceImplementation="com.google.idea.blaze.clwb.problemsview.BlazeProblemsViewConsole"/>
-    <toolWindow id="Blaze Problems View"
+    <toolWindow id="Blaze Problems"
                 anchor="bottom"
-                secondary="true"
+                secondary="false"
                 conditionClass="com.google.idea.blaze.base.settings.IsBlazeProjectCondition"
                 icon="BlazeIcons.BlazeToolWindow"
                 factoryClass="com.google.idea.blaze.clwb.problemsview.BlazeProblemsViewConsoleToolWindowFactory"/>
@@ -49,6 +49,7 @@
   <extensions defaultExtensionNs="com.google.idea.blaze">
     <SyncPlugin implementation="com.google.idea.blaze.clwb.sync.BlazeCLionSyncPlugin"/>
     <BlazeCommandRunConfigurationHandlerProvider implementation="com.google.idea.blaze.clwb.run.BlazeCidrRunConfigurationHandlerProvider" order="first"/>
+    <RunConfigurationFactory implementation="com.google.idea.blaze.clwb.run.BlazeCidrDebuggableConfigurationFactory"/>
   </extensions>
 
   <actions>
diff --git a/clwb/src/com/google/idea/blaze/clwb/problemsview/BlazeProblemsViewConsole.java b/clwb/src/com/google/idea/blaze/clwb/problemsview/BlazeProblemsViewConsole.java
index 07703e3..543f346 100644
--- a/clwb/src/com/google/idea/blaze/clwb/problemsview/BlazeProblemsViewConsole.java
+++ b/clwb/src/com/google/idea/blaze/clwb/problemsview/BlazeProblemsViewConsole.java
@@ -49,7 +49,7 @@
 import org.jetbrains.annotations.Nullable;
 import org.jetbrains.ide.PooledThreadExecutor;
 
-/** @author Eugene Zhuravlev Date: 9/18/12 */
+/** CLion has no built-in 'Problems' view, so we mostly duplicate the IntelliJ code here. */
 public class BlazeProblemsViewConsole implements BlazeProblemsView {
   private static final Logger LOG = Logger.getInstance(BlazeProblemsViewConsole.class);
 
@@ -76,6 +76,7 @@
     myProject = project;
     myPanel = new ProblemsViewPanel(project);
     Disposer.register(project, () -> Disposer.dispose(myPanel));
+    updateIcon();
   }
 
   public void createToolWindowContent(ToolWindow toolWindow) {
@@ -93,7 +94,9 @@
             : null;
     Navigatable navigatable = issue.getNavigatable();
     if (navigatable == null && file != null) {
-      navigatable = new OpenFileDescriptor(myProject, file, -1, -1);
+      // convert 1-indexed line/column numbers to 0-indexed
+      navigatable =
+          new OpenFileDescriptor(myProject, file, issue.getLine() - 1, issue.getColumn() - 1);
     }
     final IssueOutput.Category category = issue.getCategory();
     final int type = translateCategory(category);
@@ -196,44 +199,40 @@
       @Nullable final UUID sessionId) {
 
     myViewUpdater.execute(
-        new Runnable() {
-          @Override
-          public void run() {
-            final ErrorViewStructure structure = myPanel.getErrorViewStructure();
-            final GroupingElement group = structure.lookupGroupingElement(groupName);
-            if (group != null && sessionId != null && !sessionId.equals(group.getData())) {
-              structure.removeElement(group);
-            }
-            if (navigatable != null) {
-              myPanel.addMessage(
-                  type,
-                  text,
-                  groupName,
-                  navigatable,
-                  exportTextPrefix,
-                  rendererTextPrefix,
-                  sessionId);
-            } else {
-              myPanel.addMessage(type, text, null, -1, -1, sessionId);
-            }
-            updateIcon();
+        () -> {
+          final ErrorViewStructure structure = myPanel.getErrorViewStructure();
+          final GroupingElement group = structure.lookupGroupingElement(groupName);
+          if (group != null && sessionId != null && !sessionId.equals(group.getData())) {
+            structure.removeElement(group);
           }
+          if (navigatable != null) {
+            myPanel.addMessage(
+                type,
+                text,
+                groupName,
+                navigatable,
+                exportTextPrefix,
+                rendererTextPrefix,
+                sessionId);
+          } else {
+            myPanel.addMessage(type, text, null, -1, -1, sessionId);
+          }
+          updateIcon();
         });
   }
 
   private void updateIcon() {
     UIUtil.invokeLaterIfNeeded(
-        new Runnable() {
-          @Override
-          public void run() {
-            if (!myProject.isDisposed()) {
-              final ToolWindow tw =
-                  ToolWindowManager.getInstance(myProject)
-                      .getToolWindow(BLAZE_PROBLEMS_TOOLWINDOW_ID);
-              if (tw != null) {
-                final boolean active =
-                    myPanel.getErrorViewStructure().hasMessages(ALL_MESSAGE_KINDS);
-                tw.setIcon(active ? myActiveIcon : myPassiveIcon);
+        () -> {
+          if (!myProject.isDisposed()) {
+            final ToolWindow tw =
+                ToolWindowManager.getInstance(myProject)
+                    .getToolWindow(BLAZE_PROBLEMS_TOOLWINDOW_ID);
+            if (tw != null) {
+              final boolean active = myPanel.getErrorViewStructure().hasMessages(ALL_MESSAGE_KINDS);
+              tw.setIcon(active ? myActiveIcon : myPassiveIcon);
+              if (active) {
+                tw.show(null);
               }
             }
           }
diff --git a/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrDebuggableConfigurationFactory.java b/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrDebuggableConfigurationFactory.java
new file mode 100644
index 0000000..5363e69
--- /dev/null
+++ b/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrDebuggableConfigurationFactory.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.clwb.run;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.command.BlazeCommandName;
+import com.google.idea.blaze.base.command.BlazeFlags;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.Kind;
+import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
+import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
+import com.google.idea.blaze.base.run.BlazeRunConfigurationFactory;
+import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState;
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.openapi.project.Project;
+
+/**
+ * Creates run configurations for debuggable cc targets. Non-debuggable targets are handled by the
+ * default factory.
+ */
+public class BlazeCidrDebuggableConfigurationFactory extends BlazeRunConfigurationFactory {
+  @Override
+  public boolean handlesTarget(Project project, BlazeProjectData blazeProjectData, Label label) {
+    TargetIdeInfo target = blazeProjectData.targetMap.get(TargetKey.forPlainTarget(label));
+    return target != null && RunConfigurationUtils.canUseClionHandler(target.kind);
+  }
+
+  @Override
+  protected ConfigurationFactory getConfigurationFactory() {
+    return BlazeCommandRunConfigurationType.getInstance().getFactory();
+  }
+
+  @Override
+  public void setupConfiguration(RunConfiguration configuration, Label target) {
+    final BlazeCommandRunConfiguration blazeConfig = (BlazeCommandRunConfiguration) configuration;
+    blazeConfig.setTarget(target);
+
+    BlazeCommandRunConfigurationCommonState state =
+        blazeConfig.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
+    Kind kind = blazeConfig.getKindForTarget();
+    if (state != null) {
+      if (kind != null && Kind.isTestRule(kind.toString())) {
+        state.setCommand(BlazeCommandName.TEST);
+        state.setBlazeFlags(ImmutableList.of(BlazeFlags.TEST_OUTPUT_STREAMED));
+      } else {
+        state.setCommand(BlazeCommandName.RUN);
+      }
+    }
+    blazeConfig.setGeneratedName();
+  }
+}
diff --git a/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrLauncher.java b/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrLauncher.java
index 70f249e..9195ab0 100644
--- a/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrLauncher.java
+++ b/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrLauncher.java
@@ -143,6 +143,7 @@
     GeneralCommandLine commandLine = new GeneralCommandLine(runner.executableToDebug.getPath());
     File workingDir = workspaceRoot.directory();
     commandLine.setWorkDirectory(workingDir);
+    commandLine.addParameters(handlerState.getExeFlags());
 
     TrivialInstaller installer = new TrivialInstaller(commandLine);
     ImmutableList<String> startupCommands = getGdbStartupCommands(workingDir);
@@ -184,6 +185,7 @@
   private final class GoogleTestConsoleBuilder extends CidrConsoleBuilder {
     private GoogleTestConsoleBuilder(Project project) {
       super(project);
+      addFilter(new BlazeCidrTestOutputFilter(project));
     }
 
     @Override
diff --git a/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrRunConfigurationRunner.java b/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrRunConfigurationRunner.java
index 515e4bb..8c36f71 100644
--- a/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrRunConfigurationRunner.java
+++ b/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrRunConfigurationRunner.java
@@ -122,7 +122,6 @@
                     .push(new LoggedTimingScope(project, Action.BLAZE_COMMAND_USAGE))
                     .push(new IssuesScope(project))
                     .push(new BlazeConsoleScope.Builder(project).build());
-                ;
 
                 context.output(new StatusOutput("Building debug binary"));
 
@@ -130,8 +129,7 @@
                     BlazeCommand.builder(Blaze.getBuildSystem(project), BlazeCommandName.BUILD)
                         .addTargets(configuration.getTarget())
                         .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet))
-                        .addBlazeFlags(handlerState.getBlazeFlags())
-                        .addExeFlags(handlerState.getExeFlags());
+                        .addBlazeFlags(handlerState.getBlazeFlags());
 
                 command.addBlazeFlags("--experimental_show_artifacts");
 
@@ -146,7 +144,7 @@
                     .context(context)
                     .stderr(
                         LineProcessingOutputStream.of(
-                            new ExperimentalShowArtifactsLineProcessor(outputArtifacts, ""),
+                            new ExperimentalShowArtifactsLineProcessor(outputArtifacts),
                             new IssueOutputLineProcessor(project, context, workspaceRoot)))
                     .build()
                     .run();
diff --git a/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrTestOutputFilter.java b/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrTestOutputFilter.java
new file mode 100644
index 0000000..b2498f1
--- /dev/null
+++ b/clwb/src/com/google/idea/blaze/clwb/run/BlazeCidrTestOutputFilter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.clwb.run;
+
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
+import com.intellij.execution.filters.HyperlinkInfo;
+import com.intellij.execution.filters.RegexpFilter;
+import com.intellij.openapi.project.Project;
+import java.io.File;
+import org.jetbrains.annotations.Nullable;
+
+/** Identifies file paths in CLion test output which aren't found by CidrPathConsoleFilter. */
+public class BlazeCidrTestOutputFilter extends RegexpFilter {
+
+  private static final String FAILURE_REGEX =
+      String.format("%s:%s: Failure", RegexpFilter.FILE_PATH_MACROS, RegexpFilter.LINE_MACROS);
+
+  @Nullable private final WorkspacePathResolver workspacePathResolver;
+
+  public BlazeCidrTestOutputFilter(Project project) {
+    super(project, FAILURE_REGEX);
+    BlazeProjectData data = BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
+    workspacePathResolver = data != null ? data.workspacePathResolver : null;
+  }
+
+  @Nullable
+  @Override
+  protected HyperlinkInfo createOpenFileHyperlink(String fileName, int line, int column) {
+    HyperlinkInfo result = super.createOpenFileHyperlink(fileName, line, column);
+    if (result != null || workspacePathResolver == null) {
+      return result;
+    }
+    File workspaceFile = workspacePathResolver.resolveToFile(fileName);
+    return super.createOpenFileHyperlink(workspaceFile.getPath(), line, column);
+  }
+}
diff --git a/clwb/src/com/google/idea/blaze/clwb/run/RunConfigurationUtils.java b/clwb/src/com/google/idea/blaze/clwb/run/RunConfigurationUtils.java
index a6c96ad..10e7c7f 100644
--- a/clwb/src/com/google/idea/blaze/clwb/run/RunConfigurationUtils.java
+++ b/clwb/src/com/google/idea/blaze/clwb/run/RunConfigurationUtils.java
@@ -38,6 +38,6 @@
     return kind != null
         && command != null
         && ((kind == Kind.CC_TEST && command.equals(BlazeCommandName.TEST))
-            || (kind == Kind.CC_BINARY && command.equals(BlazeCommandName.BUILD)));
+            || (kind == Kind.CC_BINARY && command.equals(BlazeCommandName.RUN)));
   }
 }
diff --git a/clwb/src/com/google/idea/blaze/clwb/run/producers/BlazeCidrTestConfigurationProducer.java b/clwb/src/com/google/idea/blaze/clwb/run/producers/BlazeCidrTestConfigurationProducer.java
index 3132f87..2954d92 100644
--- a/clwb/src/com/google/idea/blaze/clwb/run/producers/BlazeCidrTestConfigurationProducer.java
+++ b/clwb/src/com/google/idea/blaze/clwb/run/producers/BlazeCidrTestConfigurationProducer.java
@@ -18,12 +18,10 @@
 import com.google.common.collect.ImmutableList;
 import com.google.idea.blaze.base.command.BlazeCommandName;
 import com.google.idea.blaze.base.command.BlazeFlags;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
 import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
-import com.google.idea.blaze.base.run.TestRuleFinder;
-import com.google.idea.blaze.base.run.TestRuleHeuristic;
+import com.google.idea.blaze.base.run.TestTargetHeuristic;
 import com.google.idea.blaze.base.run.producers.BlazeRunConfigurationProducer;
 import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState;
 import com.google.idea.blaze.base.settings.Blaze;
@@ -31,9 +29,7 @@
 import com.intellij.execution.actions.ConfigurationContext;
 import com.intellij.openapi.util.Couple;
 import com.intellij.openapi.util.Ref;
-import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
 import com.intellij.psi.util.PsiTreeUtil;
 import com.jetbrains.cidr.execution.testing.CidrTestUtil;
 import com.jetbrains.cidr.lang.psi.OCFile;
@@ -45,7 +41,6 @@
 import com.jetbrains.cidr.lang.symbols.cpp.OCFunctionSymbol;
 import com.jetbrains.cidr.lang.symbols.cpp.OCStructSymbol;
 import com.jetbrains.cidr.lang.symbols.cpp.OCSymbolWithQualifiedName;
-import java.io.File;
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
@@ -69,7 +64,7 @@
     @Nullable
     private static TestTarget createFromClassAndMethod(
         @Nullable PsiElement element, String classOrSuiteName, @Nullable String testName) {
-      Label label = getCcTestTarget(element);
+      Label label = TestTargetHeuristic.testTargetForPsiElement(element);
       if (label == null) {
         return null;
       }
@@ -247,27 +242,4 @@
     }
     return false;
   }
-
-  @Nullable
-  private static Label getCcTestTarget(@Nullable PsiElement element) {
-    if (element == null) {
-      return null;
-    }
-    File file = getContainingFile(element);
-    if (file == null) {
-      return null;
-    }
-    Collection<RuleIdeInfo> rules =
-        TestRuleFinder.getInstance(element.getProject()).testTargetsForSourceFile(file);
-    return TestRuleHeuristic.chooseTestTargetForSourceFile(file, rules, null);
-  }
-
-  private static File getContainingFile(PsiElement element) {
-    PsiFile psiFile = element.getContainingFile();
-    if (psiFile == null) {
-      return null;
-    }
-    VirtualFile vf = psiFile.getVirtualFile();
-    return vf != null ? new File(vf.getPath()) : null;
-  }
 }
diff --git a/clwb/src/com/google/idea/blaze/clwb/sync/BlazeCLionSyncPlugin.java b/clwb/src/com/google/idea/blaze/clwb/sync/BlazeCLionSyncPlugin.java
index c387715..00888bc 100644
--- a/clwb/src/com/google/idea/blaze/clwb/sync/BlazeCLionSyncPlugin.java
+++ b/clwb/src/com/google/idea/blaze/clwb/sync/BlazeCLionSyncPlugin.java
@@ -15,6 +15,7 @@
  */
 package com.google.idea.blaze.clwb.sync;
 
+import com.google.common.collect.ImmutableList;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.model.primitives.WorkspaceType;
@@ -31,6 +32,11 @@
 
 class BlazeCLionSyncPlugin extends BlazeSyncPlugin.Adapter {
 
+  @Override
+  public ImmutableList<WorkspaceType> getSupportedWorkspaceTypes() {
+    return ImmutableList.of(WorkspaceType.C);
+  }
+
   @Nullable
   @Override
   public WorkspaceType getDefaultWorkspaceType() {
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeCWorkspace.java b/cpp/src/com/google/idea/blaze/cpp/BlazeCWorkspace.java
index e2c5a60..2795722 100644
--- a/cpp/src/com/google/idea/blaze/cpp/BlazeCWorkspace.java
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeCWorkspace.java
@@ -30,7 +30,6 @@
 import com.jetbrains.cidr.lang.workspace.OCResolveConfiguration;
 import com.jetbrains.cidr.lang.workspace.OCWorkspace;
 import com.jetbrains.cidr.lang.workspace.OCWorkspaceModificationTrackers;
-import java.io.File;
 import java.util.Collection;
 import java.util.List;
 import javax.annotation.Nullable;
@@ -65,6 +64,15 @@
     LOG.assertTrue(configurationResolver != null);
 
     long start = System.currentTimeMillis();
+
+    // non-recursive refresh of the blaze-out directory. This essentially invalidates the cache for
+    // all files below this directory.
+    ApplicationManager.getApplication()
+        .runWriteAction(
+            () ->
+                LocalFileSystem.getInstance()
+                    .refreshIoFiles(ImmutableList.of(blazeProjectData.blazeRoots.executionRoot)));
+
     // Non-incremental update to our c configurations.
     configurationResolver.update(context, blazeProjectData);
     long end = System.currentTimeMillis();
@@ -77,10 +85,6 @@
               if (project.isDisposed()) {
                 return;
               }
-
-              File genfilesDir = blazeProjectData.blazeRoots.getGenfilesDirectory();
-              LocalFileSystem.getInstance().refreshIoFiles(ImmutableList.of(genfilesDir));
-
               // TODO(salguarnieri) Avoid bumping all of these trackers; figure out what has changed
               modTrackers.getProjectFilesListTracker().incModificationCount();
               modTrackers.getSourceFilesListTracker().incModificationCount();
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolver.java b/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolver.java
index c9b7c37..7ba79d8 100644
--- a/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolver.java
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeConfigurationResolver.java
@@ -15,28 +15,36 @@
  */
 package com.google.idea.blaze.cpp;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 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.common.collect.Maps;
+import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.idea.blaze.base.async.executor.BlazeExecutor;
 import com.google.idea.blaze.base.ideinfo.CToolchainIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
+import com.google.idea.blaze.base.io.FileAttributeProvider;
 import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
 import com.google.idea.blaze.base.model.primitives.LanguageClass;
-import com.google.idea.blaze.base.rulemaps.SourceToRuleMap;
 import com.google.idea.blaze.base.scope.BlazeContext;
 import com.google.idea.blaze.base.scope.Scope;
 import com.google.idea.blaze.base.scope.ScopedFunction;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
 import com.google.idea.blaze.base.scope.scopes.TimingScope;
 import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
+import com.google.idea.blaze.base.targetmaps.SourceToTargetMap;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VfsUtilCore;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.jetbrains.cidr.lang.workspace.OCResolveConfiguration;
@@ -45,6 +53,7 @@
 import java.io.PrintWriter;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutionException;
@@ -52,11 +61,11 @@
 
 final class BlazeConfigurationResolver {
   private static final class MapEntry {
-    public final RuleKey ruleKey;
+    public final TargetKey targetKey;
     public final BlazeResolveConfiguration configuration;
 
-    public MapEntry(RuleKey ruleKey, BlazeResolveConfiguration configuration) {
-      this.ruleKey = ruleKey;
+    public MapEntry(TargetKey targetKey, BlazeResolveConfiguration configuration) {
+      this.targetKey = targetKey;
       this.configuration = configuration;
     }
   }
@@ -64,7 +73,7 @@
   private static final Logger LOG = Logger.getInstance(BlazeConfigurationResolver.class);
   private final Project project;
 
-  private ImmutableMap<RuleKey, BlazeResolveConfiguration> resolveConfigurations =
+  private ImmutableMap<TargetKey, BlazeResolveConfiguration> resolveConfigurations =
       ImmutableMap.of();
 
   public BlazeConfigurationResolver(Project project) {
@@ -72,95 +81,181 @@
   }
 
   public void update(BlazeContext context, BlazeProjectData blazeProjectData) {
-    WorkspacePathResolver workspacePathResolver = blazeProjectData.workspacePathResolver;
-    ImmutableMap<RuleKey, CToolchainIdeInfo> toolchainLookupMap =
+    ImmutableMap<TargetKey, CToolchainIdeInfo> toolchainLookupMap =
         BlazeResolveConfiguration.buildToolchainLookupMap(
-            context, blazeProjectData.ruleMap, blazeProjectData.reverseDependencies);
+            context, blazeProjectData.targetMap, blazeProjectData.reverseDependencies);
+    ImmutableMap<File, VirtualFile> headerRoots =
+        collectHeaderRoots(context, blazeProjectData, toolchainLookupMap);
     resolveConfigurations =
-        buildBlazeConfigurationMap(
-            context, blazeProjectData, toolchainLookupMap, workspacePathResolver);
+        buildBlazeConfigurationMap(context, blazeProjectData, toolchainLookupMap, headerRoots);
   }
 
-  private ImmutableMap<RuleKey, BlazeResolveConfiguration> buildBlazeConfigurationMap(
+  private static ImmutableMap<File, VirtualFile> collectHeaderRoots(
       BlazeContext parentContext,
       BlazeProjectData blazeProjectData,
-      ImmutableMap<RuleKey, CToolchainIdeInfo> toolchainLookupMap,
-      WorkspacePathResolver workspacePathResolver) {
+      ImmutableMap<TargetKey, CToolchainIdeInfo> toolchainLookupMap) {
     // Type specification needed to avoid incorrect type inference during command line build.
     return Scope.push(
         parentContext,
-        (ScopedFunction<ImmutableMap<RuleKey, BlazeResolveConfiguration>>)
+        (ScopedFunction<ImmutableMap<File, VirtualFile>>)
+            context -> {
+              context.push(new TimingScope("Resolve header include roots"));
+              Set<ExecutionRootPath> paths =
+                  collectExecutionRootPaths(blazeProjectData.targetMap, toolchainLookupMap);
+              return doCollectHeaderRoots(context, blazeProjectData, paths);
+            });
+  }
+
+  private static ImmutableMap<File, VirtualFile> doCollectHeaderRoots(
+      BlazeContext context, BlazeProjectData projectData, Set<ExecutionRootPath> rootPaths) {
+    ConcurrentMap<File, VirtualFile> rootsMap = Maps.newConcurrentMap();
+    List<ListenableFuture<Void>> futures = Lists.newArrayListWithCapacity(rootPaths.size());
+    for (ExecutionRootPath path : rootPaths) {
+      futures.add(
+          submit(
+              () -> {
+                ImmutableList<File> possibleDirectories =
+                    projectData.workspacePathResolver.resolveToIncludeDirectories(path);
+                for (File file : possibleDirectories) {
+                  VirtualFile vf = getVirtualFile(file);
+                  if (vf != null) {
+                    rootsMap.put(file, vf);
+                  } else if (!projectData.blazeRoots.isOutputArtifact(path)
+                      && FileAttributeProvider.getInstance().exists(file)) {
+                    // If it's not a blaze output file, we expect it to always resolve.
+                    LOG.info(String.format("Unresolved header root %s", file.getAbsolutePath()));
+                  }
+                }
+                return null;
+              }));
+    }
+    try {
+      Futures.allAsList(futures).get();
+      return ImmutableMap.copyOf(rootsMap);
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      context.setCancelled();
+    } catch (ExecutionException e) {
+      IssueOutput.error("Error resolving header include roots: " + e).submit(context);
+      LOG.error("Error resolving header include roots", e);
+    }
+    return ImmutableMap.of();
+  }
+
+  private static Set<ExecutionRootPath> collectExecutionRootPaths(
+      TargetMap targetMap, ImmutableMap<TargetKey, CToolchainIdeInfo> toolchainLookupMap) {
+    Set<ExecutionRootPath> paths = Sets.newHashSet();
+    for (TargetIdeInfo target : targetMap.targets()) {
+      if (target.cIdeInfo != null) {
+        paths.addAll(target.cIdeInfo.transitiveSystemIncludeDirectories);
+        paths.addAll(target.cIdeInfo.transitiveIncludeDirectories);
+        paths.addAll(target.cIdeInfo.transitiveQuoteIncludeDirectories);
+      }
+    }
+    for (CToolchainIdeInfo toolchain : toolchainLookupMap.values()) {
+      paths.addAll(toolchain.builtInIncludeDirectories);
+      paths.addAll(toolchain.unfilteredToolchainSystemIncludes);
+    }
+    return paths;
+  }
+
+  @Nullable
+  private static VirtualFile getVirtualFile(File file) {
+    LocalFileSystem fileSystem = LocalFileSystem.getInstance();
+    VirtualFile vf = fileSystem.findFileByPathIfCached(file.getPath());
+    if (vf == null) {
+      vf = fileSystem.findFileByIoFile(file);
+    }
+    return vf;
+  }
+
+  private ImmutableMap<TargetKey, BlazeResolveConfiguration> buildBlazeConfigurationMap(
+      BlazeContext parentContext,
+      BlazeProjectData blazeProjectData,
+      ImmutableMap<TargetKey, CToolchainIdeInfo> toolchainLookupMap,
+      ImmutableMap<File, VirtualFile> headerRoots) {
+    // Type specification needed to avoid incorrect type inference during command line build.
+    return Scope.push(
+        parentContext,
+        (ScopedFunction<ImmutableMap<TargetKey, BlazeResolveConfiguration>>)
             context -> {
               context.push(new TimingScope("Build C configuration map"));
 
               ConcurrentMap<CToolchainIdeInfo, File> compilerWrapperCache = Maps.newConcurrentMap();
               List<ListenableFuture<MapEntry>> mapEntryFutures = Lists.newArrayList();
 
-              for (RuleIdeInfo rule : blazeProjectData.ruleMap.rules()) {
-                if (rule.kind.getLanguageClass() == LanguageClass.C) {
+              for (TargetIdeInfo target : blazeProjectData.targetMap.targets()) {
+                if (target.kind.getLanguageClass() == LanguageClass.C) {
                   ListenableFuture<MapEntry> future =
                       submit(
                           () ->
                               createResolveConfiguration(
-                                  rule,
+                                  target,
                                   toolchainLookupMap,
+                                  headerRoots,
                                   compilerWrapperCache,
-                                  workspacePathResolver,
                                   blazeProjectData));
                   mapEntryFutures.add(future);
                 }
               }
 
-              ImmutableMap.Builder<RuleKey, BlazeResolveConfiguration> newResolveConfigurations =
+              ImmutableMap.Builder<TargetKey, BlazeResolveConfiguration> newResolveConfigurations =
                   ImmutableMap.builder();
               List<MapEntry> mapEntries;
               try {
                 mapEntries = Futures.allAsList(mapEntryFutures).get();
-              } catch (InterruptedException | ExecutionException e) {
+              } catch (InterruptedException e) {
                 Thread.currentThread().interrupt();
-                LOG.warn("Could not build C resolve configurations", e);
                 context.setCancelled();
                 return ImmutableMap.of();
+              } catch (ExecutionException e) {
+                IssueOutput.error("Could not build C resolve configurations: " + e).submit(context);
+                LOG.error("Could not build C resolve configurations", e);
+                return ImmutableMap.of();
               }
 
               for (MapEntry mapEntry : mapEntries) {
                 // Skip over labels that don't have C configuration data.
                 if (mapEntry != null) {
-                  newResolveConfigurations.put(mapEntry.ruleKey, mapEntry.configuration);
+                  newResolveConfigurations.put(mapEntry.targetKey, mapEntry.configuration);
                 }
               }
               return newResolveConfigurations.build();
             });
   }
 
-  private static ListenableFuture<MapEntry> submit(Callable<MapEntry> callable) {
+  private static <T> ListenableFuture<T> submit(Callable<T> callable) {
     return BlazeExecutor.getInstance().submit(callable);
   }
 
   @Nullable
   private MapEntry createResolveConfiguration(
-      RuleIdeInfo rule,
-      ImmutableMap<RuleKey, CToolchainIdeInfo> toolchainLookupMap,
+      TargetIdeInfo target,
+      ImmutableMap<TargetKey, CToolchainIdeInfo> toolchainLookupMap,
+      ImmutableMap<File, VirtualFile> headerRoots,
       ConcurrentMap<CToolchainIdeInfo, File> compilerWrapperCache,
-      WorkspacePathResolver workspacePathResolver,
       BlazeProjectData blazeProjectData) {
-    RuleKey ruleKey = rule.key;
+    TargetKey targetKey = target.key;
 
-    CToolchainIdeInfo toolchainIdeInfo = toolchainLookupMap.get(ruleKey);
+    CToolchainIdeInfo toolchainIdeInfo = toolchainLookupMap.get(targetKey);
     if (toolchainIdeInfo != null) {
       File compilerWrapper =
           findOrCreateCompilerWrapperScript(
-              compilerWrapperCache, toolchainIdeInfo, workspacePathResolver, ruleKey);
+              compilerWrapperCache,
+              toolchainIdeInfo,
+              blazeProjectData.workspacePathResolver,
+              targetKey);
       if (compilerWrapper != null) {
         BlazeResolveConfiguration config =
             BlazeResolveConfiguration.createConfigurationForTarget(
                 project,
-                workspacePathResolver,
-                blazeProjectData.ruleMap.get(ruleKey),
+                blazeProjectData.workspacePathResolver,
+                headerRoots,
+                blazeProjectData.targetMap.get(targetKey),
                 toolchainIdeInfo,
                 compilerWrapper);
         if (config != null) {
-          return new MapEntry(ruleKey, config);
+          return new MapEntry(targetKey, config);
         }
       }
     }
@@ -172,7 +267,7 @@
       Map<CToolchainIdeInfo, File> compilerWrapperCache,
       CToolchainIdeInfo toolchainIdeInfo,
       WorkspacePathResolver workspacePathResolver,
-      RuleKey ruleKey) {
+      TargetKey targetKey) {
     File compilerWrapper = compilerWrapperCache.get(toolchainIdeInfo);
     if (compilerWrapper == null) {
       File cppExecutable = toolchainIdeInfo.cppExecutable.getAbsoluteOrRelativeFile();
@@ -183,7 +278,7 @@
         String errorMessage =
             String.format(
                 "Unable to find compiler executable: %s for rule %s",
-                toolchainIdeInfo.cppExecutable.toString(), ruleKey);
+                toolchainIdeInfo.cppExecutable.toString(), targetKey);
         LOG.warn(errorMessage);
         compilerWrapper = null;
       } else {
@@ -240,7 +335,7 @@
               "ARGS=`cat $ARG_FILE`",
               "$EXE $ARGS $2");
 
-      try (PrintWriter pw = new PrintWriter(blazeCompilerWrapper)) {
+      try (PrintWriter pw = new PrintWriter(blazeCompilerWrapper, UTF_8.name())) {
         compilerWrapperScriptLines.forEach(pw::println);
       }
       return blazeCompilerWrapper;
@@ -251,10 +346,10 @@
 
   @Nullable
   public OCResolveConfiguration getConfigurationForFile(VirtualFile sourceFile) {
-    SourceToRuleMap sourceToRuleMap = SourceToRuleMap.getInstance(project);
-    List<RuleKey> targetsForSourceFile =
+    SourceToTargetMap sourceToTargetMap = SourceToTargetMap.getInstance(project);
+    List<TargetKey> targetsForSourceFile =
         Lists.newArrayList(
-            sourceToRuleMap.getRulesForSourceFile(VfsUtilCore.virtualToIoFile(sourceFile)));
+            sourceToTargetMap.getRulesForSourceFile(VfsUtilCore.virtualToIoFile(sourceFile)));
     if (targetsForSourceFile.isEmpty()) {
       return null;
     }
@@ -263,10 +358,10 @@
     // we can't possibly show how it will be interpreted in both contexts at the same time
     // in the IDE, so just pick the first target after we sort.
     targetsForSourceFile.sort((o1, o2) -> o1.toString().compareTo(o2.toString()));
-    RuleKey ruleKey = Iterables.getFirst(targetsForSourceFile, null);
-    assert (ruleKey != null);
+    TargetKey targetKey = Iterables.getFirst(targetsForSourceFile, null);
+    assert (targetKey != null);
 
-    return resolveConfigurations.get(ruleKey);
+    return resolveConfigurations.get(targetKey);
   }
 
   public List<? extends OCResolveConfiguration> getAllConfigurations() {
diff --git a/cpp/src/com/google/idea/blaze/cpp/BlazeResolveConfigurationTemporaryBase.java b/cpp/src/com/google/idea/blaze/cpp/BlazeResolveConfigurationTemporaryBase.java
index a2a2b77..f32f97d 100644
--- a/cpp/src/com/google/idea/blaze/cpp/BlazeResolveConfigurationTemporaryBase.java
+++ b/cpp/src/com/google/idea/blaze/cpp/BlazeResolveConfigurationTemporaryBase.java
@@ -22,11 +22,11 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-import com.google.idea.blaze.base.ideinfo.CRuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.CIdeInfo;
 import com.google.idea.blaze.base.ideinfo.CToolchainIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
 import com.google.idea.blaze.base.scope.BlazeContext;
 import com.google.idea.blaze.base.scope.Scope;
@@ -35,7 +35,6 @@
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.UserDataHolderBase;
-import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.jetbrains.cidr.lang.OCFileTypeHelpers;
 import com.jetbrains.cidr.lang.OCLanguageKind;
@@ -72,7 +71,7 @@
 
   /* project, label are protected instead of private just so v145 can access */
   protected final Project project;
-  protected final RuleKey ruleKey;
+  protected final TargetKey targetKey;
 
   private final ImmutableList<HeadersSearchRoot> cLibraryIncludeRoots;
   private final ImmutableList<HeadersSearchRoot> cppLibraryIncludeRoots;
@@ -86,24 +85,25 @@
   public static BlazeResolveConfiguration createConfigurationForTarget(
       Project project,
       WorkspacePathResolver workspacePathResolver,
-      RuleIdeInfo ruleIdeInfo,
+      ImmutableMap<File, VirtualFile> headerRoots,
+      TargetIdeInfo target,
       CToolchainIdeInfo toolchainIdeInfo,
       File compilerWrapper) {
-    CRuleIdeInfo cRuleIdeInfo = ruleIdeInfo.cRuleIdeInfo;
-    if (cRuleIdeInfo == null) {
+    CIdeInfo cIdeInfo = target.cIdeInfo;
+    if (cIdeInfo == null) {
       return null;
     }
 
     ImmutableSet.Builder<ExecutionRootPath> systemIncludesBuilder = ImmutableSet.builder();
-    systemIncludesBuilder.addAll(cRuleIdeInfo.transitiveSystemIncludeDirectories);
+    systemIncludesBuilder.addAll(cIdeInfo.transitiveSystemIncludeDirectories);
     systemIncludesBuilder.addAll(toolchainIdeInfo.builtInIncludeDirectories);
     systemIncludesBuilder.addAll(toolchainIdeInfo.unfilteredToolchainSystemIncludes);
 
     ImmutableSet.Builder<ExecutionRootPath> userIncludesBuilder = ImmutableSet.builder();
-    userIncludesBuilder.addAll(cRuleIdeInfo.transitiveIncludeDirectories);
+    userIncludesBuilder.addAll(cIdeInfo.transitiveIncludeDirectories);
 
     ImmutableSet.Builder<ExecutionRootPath> userQuoteIncludesBuilder = ImmutableSet.builder();
-    userQuoteIncludesBuilder.addAll(cRuleIdeInfo.transitiveQuoteIncludeDirectories);
+    userQuoteIncludesBuilder.addAll(cIdeInfo.transitiveQuoteIncludeDirectories);
 
     ImmutableList.Builder<String> cFlagsBuilder = ImmutableList.builder();
     cFlagsBuilder.addAll(toolchainIdeInfo.baseCompilerOptions);
@@ -120,13 +120,14 @@
     return new BlazeResolveConfiguration(
         project,
         workspacePathResolver,
-        ruleIdeInfo.key,
+        headerRoots,
+        target.key,
         systemIncludesBuilder.build(),
         systemIncludesBuilder.build(),
         userQuoteIncludesBuilder.build(),
         userIncludesBuilder.build(),
         userIncludesBuilder.build(),
-        cRuleIdeInfo.transitiveDefines,
+        cIdeInfo.transitiveDefines,
         features,
         compilerWrapper,
         compilerWrapper,
@@ -134,31 +135,31 @@
         cppFlagsBuilder.build());
   }
 
-  public static ImmutableMap<RuleKey, CToolchainIdeInfo> buildToolchainLookupMap(
+  public static ImmutableMap<TargetKey, CToolchainIdeInfo> buildToolchainLookupMap(
       BlazeContext context,
-      RuleMap ruleMap,
-      ImmutableMultimap<RuleKey, RuleKey> reverseDependencies) {
+      TargetMap targetMap,
+      ImmutableMultimap<TargetKey, TargetKey> reverseDependencies) {
     return Scope.push(
         context,
         childContext -> {
           childContext.push(new TimingScope("Build toolchain lookup map"));
 
-          List<RuleKey> seeds = Lists.newArrayList();
-          for (RuleIdeInfo rule : ruleMap.rules()) {
-            CToolchainIdeInfo cToolchainIdeInfo = rule.cToolchainIdeInfo;
+          List<TargetKey> seeds = Lists.newArrayList();
+          for (TargetIdeInfo target : targetMap.targets()) {
+            CToolchainIdeInfo cToolchainIdeInfo = target.cToolchainIdeInfo;
             if (cToolchainIdeInfo != null) {
-              seeds.add(rule.key);
+              seeds.add(target.key);
             }
           }
 
-          Map<RuleKey, CToolchainIdeInfo> lookupTable = Maps.newHashMap();
-          for (RuleKey seed : seeds) {
-            CToolchainIdeInfo toolchainInfo = ruleMap.get(seed).cToolchainIdeInfo;
+          Map<TargetKey, CToolchainIdeInfo> lookupTable = Maps.newHashMap();
+          for (TargetKey seed : seeds) {
+            CToolchainIdeInfo toolchainInfo = targetMap.get(seed).cToolchainIdeInfo;
             LOG.assertTrue(toolchainInfo != null);
-            List<RuleKey> worklist = Lists.newArrayList(reverseDependencies.get(seed));
+            List<TargetKey> worklist = Lists.newArrayList(reverseDependencies.get(seed));
             while (!worklist.isEmpty()) {
               // We should never see a label depend on two different toolchains.
-              RuleKey l = worklist.remove(0);
+              TargetKey l = worklist.remove(0);
               CToolchainIdeInfo previousValue = lookupTable.putIfAbsent(l, toolchainInfo);
               // Don't propagate the toolchain twice.
               if (previousValue == null) {
@@ -175,7 +176,8 @@
   public BlazeResolveConfigurationTemporaryBase(
       Project project,
       WorkspacePathResolver workspacePathResolver,
-      RuleKey ruleKey,
+      ImmutableMap<File, VirtualFile> headerRoots,
+      TargetKey targetKey,
       ImmutableCollection<ExecutionRootPath> cSystemIncludeDirs,
       ImmutableCollection<ExecutionRootPath> cppSystemIncludeDirs,
       ImmutableCollection<ExecutionRootPath> quoteIncludeDirs,
@@ -189,20 +191,24 @@
       ImmutableList<String> cppCompilerFlags) {
     this.workspacePathResolver = workspacePathResolver;
     this.project = project;
-    this.ruleKey = ruleKey;
+    this.targetKey = targetKey;
 
     ImmutableList.Builder<HeadersSearchRoot> cIncludeRootsBuilder = ImmutableList.builder();
-    collectHeaderRoots(cIncludeRootsBuilder, cIncludeDirs, true /* isUserHeader */);
-    collectHeaderRoots(cIncludeRootsBuilder, cSystemIncludeDirs, false /* isUserHeader */);
+    collectHeaderRoots(headerRoots, cIncludeRootsBuilder, cIncludeDirs, true /* isUserHeader */);
+    collectHeaderRoots(
+        headerRoots, cIncludeRootsBuilder, cSystemIncludeDirs, false /* isUserHeader */);
     this.cLibraryIncludeRoots = cIncludeRootsBuilder.build();
 
     ImmutableList.Builder<HeadersSearchRoot> cppIncludeRootsBuilder = ImmutableList.builder();
-    collectHeaderRoots(cppIncludeRootsBuilder, cppIncludeDirs, true /* isUserHeader */);
-    collectHeaderRoots(cppIncludeRootsBuilder, cppSystemIncludeDirs, false /* isUserHeader */);
+    collectHeaderRoots(
+        headerRoots, cppIncludeRootsBuilder, cppIncludeDirs, true /* isUserHeader */);
+    collectHeaderRoots(
+        headerRoots, cppIncludeRootsBuilder, cppSystemIncludeDirs, false /* isUserHeader */);
     this.cppLibraryIncludeRoots = cppIncludeRootsBuilder.build();
 
     ImmutableList.Builder<HeadersSearchRoot> quoteIncludeRootsBuilder = ImmutableList.builder();
-    collectHeaderRoots(quoteIncludeRootsBuilder, quoteIncludeDirs, true /* isUserHeader */);
+    collectHeaderRoots(
+        headerRoots, quoteIncludeRootsBuilder, quoteIncludeDirs, true /* isUserHeader */);
     this.projectIncludeRoots = new HeaderRoots(quoteIncludeRootsBuilder.build());
 
     this.compilerSettings =
@@ -225,7 +231,7 @@
 
   @Override
   public String getDisplayName(boolean shorten) {
-    return ruleKey.toString();
+    return targetKey.toString();
   }
 
   @Nullable
@@ -308,6 +314,7 @@
   }
 
   private void collectHeaderRoots(
+      ImmutableMap<File, VirtualFile> virtualFileCache,
       ImmutableList.Builder<HeadersSearchRoot> roots,
       ImmutableCollection<ExecutionRootPath> paths,
       boolean isUserHeader) {
@@ -315,28 +322,14 @@
       ImmutableList<File> possibleDirectories =
           workspacePathResolver.resolveToIncludeDirectories(executionRootPath);
       for (File f : possibleDirectories) {
-        VirtualFile vf = getVirtualFile(f);
-        if (vf == null) {
-          LOG.debug(
-              String.format(
-                  "Header root %s could not be converted to a virtual file", f.getAbsolutePath()));
-        } else {
+        VirtualFile vf = virtualFileCache.get(f);
+        if (vf != null) {
           roots.add(new IncludedHeadersRoot(project, vf, false /* recursive */, isUserHeader));
         }
       }
     }
   }
 
-  @Nullable
-  private static VirtualFile getVirtualFile(File file) {
-    LocalFileSystem fileSystem = LocalFileSystem.getInstance();
-    VirtualFile vf = fileSystem.findFileByPathIfCached(file.getPath());
-    if (vf == null) {
-      vf = fileSystem.findFileByIoFile(file);
-    }
-    return vf;
-  }
-
   @Override
   public OCCompilerMacros getCompilerMacros() {
     return compilerMacros;
@@ -361,7 +354,7 @@
   @Override
   public int hashCode() {
     // There should only be one configuration per target.
-    return Objects.hash(ruleKey);
+    return Objects.hash(targetKey);
   }
 
   @Override
diff --git a/cpp/src/com/google/idea/blaze/cpp/versioned/v145/BlazeResolveConfiguration.java b/cpp/src/com/google/idea/blaze/cpp/versioned/v145/BlazeResolveConfiguration.java
index 9671707..0d99ab5 100644
--- a/cpp/src/com/google/idea/blaze/cpp/versioned/v145/BlazeResolveConfiguration.java
+++ b/cpp/src/com/google/idea/blaze/cpp/versioned/v145/BlazeResolveConfiguration.java
@@ -18,10 +18,11 @@
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
 import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
 import com.jetbrains.cidr.execution.CidrBuildTarget;
 import com.jetbrains.cidr.execution.CidrBuildTargetWithConfigurations;
 import com.jetbrains.cidr.execution.CidrTargetHolder;
@@ -36,7 +37,8 @@
   public BlazeResolveConfiguration(
       Project project,
       WorkspacePathResolver workspacePathResolver,
-      RuleKey ruleKey,
+      ImmutableMap<File, VirtualFile> headerRoots,
+      TargetKey targetKey,
       ImmutableCollection<ExecutionRootPath> cSystemIncludeDirs,
       ImmutableCollection<ExecutionRootPath> cppSystemIncludeDirs,
       ImmutableCollection<ExecutionRootPath> quoteIncludeDirs,
@@ -51,7 +53,8 @@
     super(
         project,
         workspacePathResolver,
-        ruleKey,
+        headerRoots,
+        targetKey,
         cSystemIncludeDirs,
         cppSystemIncludeDirs,
         quoteIncludeDirs,
@@ -71,7 +74,7 @@
     return new CidrBuildTargetWithConfigurations() {
       @Override
       public String getName() {
-        return ruleKey.toString();
+        return targetKey.toString();
       }
 
       @Override
diff --git a/cpp/src/com/google/idea/blaze/cpp/versioned/v162/BlazeResolveConfiguration.java b/cpp/src/com/google/idea/blaze/cpp/versioned/v162/BlazeResolveConfiguration.java
index 8935576..71ba414 100644
--- a/cpp/src/com/google/idea/blaze/cpp/versioned/v162/BlazeResolveConfiguration.java
+++ b/cpp/src/com/google/idea/blaze/cpp/versioned/v162/BlazeResolveConfiguration.java
@@ -18,10 +18,11 @@
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
 import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
 import java.io.File;
 
 final class BlazeResolveConfiguration extends BlazeResolveConfigurationTemporaryBase {
@@ -29,7 +30,8 @@
   public BlazeResolveConfiguration(
       Project project,
       WorkspacePathResolver workspacePathResolver,
-      RuleKey ruleKey,
+      ImmutableMap<File, VirtualFile> headerRoots,
+      TargetKey targetKey,
       ImmutableCollection<ExecutionRootPath> cSystemIncludeDirs,
       ImmutableCollection<ExecutionRootPath> cppSystemIncludeDirs,
       ImmutableCollection<ExecutionRootPath> quoteIncludeDirs,
@@ -44,7 +46,8 @@
     super(
         project,
         workspacePathResolver,
-        ruleKey,
+        headerRoots,
+        targetKey,
         cSystemIncludeDirs,
         cppSystemIncludeDirs,
         quoteIncludeDirs,
diff --git a/ijwb/BUILD b/ijwb/BUILD
index c9524bd..d327898 100644
--- a/ijwb/BUILD
+++ b/ijwb/BUILD
@@ -63,3 +63,24 @@
         ":ijwb_lib",
     ],
 )
+
+load(
+    "//testing:test_defs.bzl",
+    "intellij_unit_test_suite",
+)
+
+intellij_unit_test_suite(
+    name = "unit_tests",
+    srcs = glob(["tests/unittests/**/*.java"]),
+    test_package_root = "com.google.idea.blaze.ijwb",
+    deps = [
+        ":ijwb_lib",
+        "//base",
+        "//base:unit_test_utils",
+        "//common/experiments",
+        "//common/experiments:unit_test_utils",
+        "//intellij_platform_sdk:plugin_api_for_tests",
+        "@jsr305_annotations//jar",
+        "@junit//jar",
+    ],
+)
diff --git a/ijwb/src/META-INF/ijwb.xml b/ijwb/src/META-INF/ijwb.xml
index b5fcb39..e4dc577 100644
--- a/ijwb/src/META-INF/ijwb.xml
+++ b/ijwb/src/META-INF/ijwb.xml
@@ -27,8 +27,6 @@
     <SyncPlugin implementation="com.google.idea.blaze.ijwb.typescript.BlazeTypescriptSyncPlugin"/>
     <SyncPlugin implementation="com.google.idea.blaze.ijwb.dart.BlazeDartSyncPlugin"/>
     <java.JavaSyncAugmenter implementation="com.google.idea.blaze.ijwb.android.BlazeAndroidLiteJavaSyncAugmenter"/>
-    <java.JavaSyncAugmenter implementation="com.google.idea.blaze.ijwb.typescript.BlazeTypescriptJavaSyncAugmenter"/>
-    <java.JavaSyncAugmenter implementation="com.google.idea.blaze.ijwb.dart.BlazeDartJavaSyncAugmenter"/>
   </extensions>
 
 </idea-plugin>
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteJavaSyncAugmenter.java b/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteJavaSyncAugmenter.java
index 8aaa2b5..6a2b844 100644
--- a/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteJavaSyncAugmenter.java
+++ b/ijwb/src/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteJavaSyncAugmenter.java
@@ -15,9 +15,9 @@
  */
 package com.google.idea.blaze.ijwb.android;
 
-import com.google.idea.blaze.base.ideinfo.AndroidRuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo;
 import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.model.primitives.LanguageClass;
 import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
 import com.google.idea.blaze.java.sync.BlazeJavaSyncAugmenter;
@@ -25,30 +25,32 @@
 import java.util.Collection;
 
 /** Augments the java sync process with Android lite support. */
-public class BlazeAndroidLiteJavaSyncAugmenter extends BlazeJavaSyncAugmenter.Adapter {
+public class BlazeAndroidLiteJavaSyncAugmenter implements BlazeJavaSyncAugmenter {
 
   @Override
-  public boolean isActive(WorkspaceLanguageSettings workspaceLanguageSettings) {
-    return workspaceLanguageSettings.isLanguageActive(LanguageClass.ANDROID);
-  }
+  public void addJarsForSourceTarget(
+      WorkspaceLanguageSettings workspaceLanguageSettings,
+      TargetIdeInfo target,
+      Collection<BlazeJarLibrary> jars,
+      Collection<BlazeJarLibrary> genJars) {
+    if (!workspaceLanguageSettings.isLanguageActive(LanguageClass.ANDROID)) {
+      return;
+    }
 
-  @Override
-  public void addJarsForSourceRule(
-      RuleIdeInfo rule, Collection<BlazeJarLibrary> jars, Collection<BlazeJarLibrary> genJars) {
-    AndroidRuleIdeInfo androidRuleIdeInfo = rule.androidRuleIdeInfo;
-    if (androidRuleIdeInfo == null) {
+    AndroidIdeInfo androidIdeInfo = target.androidIdeInfo;
+    if (androidIdeInfo == null) {
       return;
     }
 
     // Add R.java jars
-    LibraryArtifact resourceJar = androidRuleIdeInfo.resourceJar;
+    LibraryArtifact resourceJar = androidIdeInfo.resourceJar;
     if (resourceJar != null) {
-      jars.add(new BlazeJarLibrary(resourceJar, rule.key));
+      jars.add(new BlazeJarLibrary(resourceJar, target.key));
     }
 
-    LibraryArtifact idlJar = androidRuleIdeInfo.idlJar;
+    LibraryArtifact idlJar = androidIdeInfo.idlJar;
     if (idlJar != null) {
-      genJars.add(new BlazeJarLibrary(idlJar, rule.key));
+      genJars.add(new BlazeJarLibrary(idlJar, target.key));
     }
   }
 }
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/dart/BlazeDartJavaSyncAugmenter.java b/ijwb/src/com/google/idea/blaze/ijwb/dart/BlazeDartJavaSyncAugmenter.java
deleted file mode 100644
index 98f0f65..0000000
--- a/ijwb/src/com/google/idea/blaze/ijwb/dart/BlazeDartJavaSyncAugmenter.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2016 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.idea.blaze.ijwb.dart;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.model.primitives.LanguageClass;
-import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
-import com.google.idea.blaze.java.sync.BlazeJavaSyncAugmenter;
-import java.util.Collection;
-
-/** Prevents garbage collection of the Dart SDK library. */
-public class BlazeDartJavaSyncAugmenter extends BlazeJavaSyncAugmenter.Adapter {
-  @Override
-  public boolean isActive(WorkspaceLanguageSettings workspaceLanguageSettings) {
-    return workspaceLanguageSettings.isLanguageActive(LanguageClass.DART);
-  }
-
-  @Override
-  public Collection<String> getExternallyAddedLibraries(BlazeProjectData blazeProjectData) {
-    return ImmutableList.of(BlazeDartSyncPlugin.DART_SDK_LIBRARY_NAME);
-  }
-}
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/dart/BlazeDartLibrarySource.java b/ijwb/src/com/google/idea/blaze/ijwb/dart/BlazeDartLibrarySource.java
new file mode 100644
index 0000000..8772ee3
--- /dev/null
+++ b/ijwb/src/com/google/idea/blaze/ijwb/dart/BlazeDartLibrarySource.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.ijwb.dart;
+
+import com.google.idea.blaze.base.sync.libraries.LibrarySource;
+import com.intellij.openapi.roots.libraries.Library;
+import java.util.function.Predicate;
+import javax.annotation.Nullable;
+
+/** Prevents garbage collection of the Dart SDK library. */
+class BlazeDartLibrarySource extends LibrarySource.Adapter {
+
+  @Nullable
+  @Override
+  public Predicate<Library> getGcRetentionFilter() {
+    return library -> {
+      String libraryName = library.getName();
+      return libraryName != null && libraryName.equals(BlazeDartSyncPlugin.DART_SDK_LIBRARY_NAME);
+    };
+  }
+}
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/dart/BlazeDartSyncPlugin.java b/ijwb/src/com/google/idea/blaze/ijwb/dart/BlazeDartSyncPlugin.java
index d9245cd..f0049d7 100644
--- a/ijwb/src/com/google/idea/blaze/ijwb/dart/BlazeDartSyncPlugin.java
+++ b/ijwb/src/com/google/idea/blaze/ijwb/dart/BlazeDartSyncPlugin.java
@@ -24,6 +24,7 @@
 import com.google.idea.blaze.base.scope.BlazeContext;
 import com.google.idea.blaze.base.scope.output.IssueOutput;
 import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.google.idea.blaze.base.sync.libraries.LibrarySource;
 import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
 import com.google.idea.blaze.ijwb.ide.IdeCheck;
 import com.intellij.openapi.module.Module;
@@ -89,4 +90,13 @@
     }
     return true;
   }
+
+  @Nullable
+  @Override
+  public LibrarySource getLibrarySource(BlazeProjectData blazeProjectData) {
+    if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.DART)) {
+      return null;
+    }
+    return new BlazeDartLibrarySource();
+  }
 }
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/javascript/BlazeJavascriptLibrarySource.java b/ijwb/src/com/google/idea/blaze/ijwb/javascript/BlazeJavascriptLibrarySource.java
new file mode 100644
index 0000000..56458cf
--- /dev/null
+++ b/ijwb/src/com/google/idea/blaze/ijwb/javascript/BlazeJavascriptLibrarySource.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.ijwb.javascript;
+
+import com.google.idea.blaze.base.sync.libraries.LibrarySource;
+import com.intellij.openapi.roots.impl.libraries.LibraryEx;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.roots.libraries.LibraryKind;
+import java.util.function.Predicate;
+import javax.annotation.Nullable;
+
+/** Prevents garbage collection of javascript libraries. */
+class BlazeJavascriptLibrarySource extends LibrarySource.Adapter {
+
+  @Nullable static final LibraryKind JS_LIBRARY_KIND = LibraryKind.findById("javaScript");
+
+  @Nullable
+  @Override
+  public Predicate<Library> getGcRetentionFilter() {
+    if (JS_LIBRARY_KIND == null) {
+      return null;
+    }
+    return BlazeJavascriptLibrarySource::isJavascriptLibrary;
+  }
+
+  static boolean isJavascriptLibrary(Library library) {
+    return JS_LIBRARY_KIND != null
+        && library instanceof LibraryEx
+        && JS_LIBRARY_KIND.equals(((LibraryEx) library).getKind());
+  }
+}
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/javascript/BlazeJavascriptSyncPlugin.java b/ijwb/src/com/google/idea/blaze/ijwb/javascript/BlazeJavascriptSyncPlugin.java
index 282a249..4d19ffc 100644
--- a/ijwb/src/com/google/idea/blaze/ijwb/javascript/BlazeJavascriptSyncPlugin.java
+++ b/ijwb/src/com/google/idea/blaze/ijwb/javascript/BlazeJavascriptSyncPlugin.java
@@ -15,7 +15,9 @@
  */
 package com.google.idea.blaze.ijwb.javascript;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.LanguageClass;
 import com.google.idea.blaze.base.model.primitives.WorkspacePath;
@@ -25,15 +27,21 @@
 import com.google.idea.blaze.base.scope.BlazeContext;
 import com.google.idea.blaze.base.scope.output.IssueOutput;
 import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.google.idea.blaze.base.sync.libraries.LibrarySource;
 import com.google.idea.blaze.base.sync.projectview.SourceTestConfig;
 import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
+import com.intellij.openapi.module.Module;
 import com.intellij.openapi.module.ModuleType;
 import com.intellij.openapi.module.WebModuleType;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.roots.ContentEntry;
+import com.intellij.openapi.roots.ModifiableRootModel;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.util.PlatformUtils;
 import java.util.Collection;
+import java.util.List;
 import java.util.Set;
 import javax.annotation.Nullable;
 
@@ -50,6 +58,11 @@
   }
 
   @Override
+  public ImmutableList<WorkspaceType> getSupportedWorkspaceTypes() {
+    return ImmutableList.of(WorkspaceType.JAVASCRIPT);
+  }
+
+  @Override
   public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
     return ImmutableSet.of(LanguageClass.JAVASCRIPT);
   }
@@ -82,6 +95,53 @@
   }
 
   @Override
+  public void updateProjectStructure(
+      Project project,
+      BlazeContext context,
+      WorkspaceRoot workspaceRoot,
+      ProjectViewSet projectViewSet,
+      BlazeProjectData blazeProjectData,
+      @Nullable BlazeProjectData oldBlazeProjectData,
+      ModuleEditor moduleEditor,
+      Module workspaceModule,
+      ModifiableRootModel workspaceModifiableModel) {
+    if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.JAVASCRIPT)
+        || BlazeJavascriptLibrarySource.JS_LIBRARY_KIND == null) {
+      return;
+    }
+    for (Library lib : getJavascriptLibraries(project)) {
+      if (workspaceModifiableModel.findLibraryOrderEntry(lib) == null) {
+        workspaceModifiableModel.addLibraryEntry(lib);
+      }
+    }
+  }
+
+  private static List<Library> getJavascriptLibraries(Project project) {
+    List<Library> libraries = Lists.newArrayList();
+    LibraryTablesRegistrar registrar = LibraryTablesRegistrar.getInstance();
+    for (Library lib : registrar.getLibraryTable().getLibraries()) {
+      if (BlazeJavascriptLibrarySource.isJavascriptLibrary(lib)) {
+        libraries.add(lib);
+      }
+    }
+    for (Library lib : registrar.getLibraryTable(project).getLibraries()) {
+      if (BlazeJavascriptLibrarySource.isJavascriptLibrary(lib)) {
+        libraries.add(lib);
+      }
+    }
+    return libraries;
+  }
+
+  @Nullable
+  @Override
+  public LibrarySource getLibrarySource(BlazeProjectData blazeProjectData) {
+    if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.JAVASCRIPT)) {
+      return null;
+    }
+    return new BlazeJavascriptLibrarySource();
+  }
+
+  @Override
   public boolean validateProjectView(
       BlazeContext context,
       ProjectViewSet projectViewSet,
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptJavaSyncAugmenter.java b/ijwb/src/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptJavaSyncAugmenter.java
deleted file mode 100644
index a39c63e..0000000
--- a/ijwb/src/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptJavaSyncAugmenter.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2016 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.idea.blaze.ijwb.typescript;
-
-import com.google.common.collect.ImmutableList;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.model.primitives.LanguageClass;
-import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
-import com.google.idea.blaze.java.sync.BlazeJavaSyncAugmenter;
-import java.util.Collection;
-
-/** Prevents garbage collection of "tsconfig$roots" */
-public class BlazeTypescriptJavaSyncAugmenter extends BlazeJavaSyncAugmenter.Adapter {
-  @Override
-  public boolean isActive(WorkspaceLanguageSettings workspaceLanguageSettings) {
-    return workspaceLanguageSettings.isLanguageActive(LanguageClass.TYPESCRIPT);
-  }
-
-  @Override
-  public Collection<String> getExternallyAddedLibraries(BlazeProjectData blazeProjectData) {
-    return ImmutableList.of(BlazeTypescriptSyncPlugin.TSCONFIG_LIBRARY_NAME);
-  }
-}
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptLibrarySource.java b/ijwb/src/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptLibrarySource.java
new file mode 100644
index 0000000..9fd0e3c
--- /dev/null
+++ b/ijwb/src/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptLibrarySource.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.ijwb.typescript;
+
+import com.google.idea.blaze.base.sync.libraries.LibrarySource;
+import com.intellij.openapi.roots.libraries.Library;
+import java.util.function.Predicate;
+import javax.annotation.Nullable;
+
+/** Prevents garbage collection of "tsconfig$roots" */
+class BlazeTypescriptLibrarySource extends LibrarySource.Adapter {
+  @Nullable
+  @Override
+  public Predicate<Library> getGcRetentionFilter() {
+    return library -> {
+      String libraryName = library.getName();
+      return libraryName != null
+          && libraryName.equals(BlazeTypescriptSyncPlugin.TSCONFIG_LIBRARY_NAME);
+    };
+  }
+}
diff --git a/ijwb/src/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptSyncPlugin.java b/ijwb/src/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptSyncPlugin.java
index 726b50e..e013c14 100644
--- a/ijwb/src/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptSyncPlugin.java
+++ b/ijwb/src/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptSyncPlugin.java
@@ -22,7 +22,7 @@
 import com.google.idea.blaze.base.command.BlazeCommand;
 import com.google.idea.blaze.base.command.BlazeCommandName;
 import com.google.idea.blaze.base.command.BlazeFlags;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import com.google.idea.blaze.base.issueparser.IssueOutputLineProcessor;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.SyncState;
@@ -39,6 +39,7 @@
 import com.google.idea.blaze.base.scope.scopes.TimingScope;
 import com.google.idea.blaze.base.settings.Blaze;
 import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.google.idea.blaze.base.sync.libraries.LibrarySource;
 import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
 import com.google.idea.blaze.base.sync.workspace.BlazeRoots;
@@ -75,7 +76,7 @@
       @Nullable WorkingSet workingSet,
       WorkspacePathResolver workspacePathResolver,
       ArtifactLocationDecoder artifactLocationDecoder,
-      RuleMap ruleMap,
+      TargetMap targetMap,
       SyncState.Builder syncStateBuilder,
       @Nullable SyncState previousSyncState) {
     if (!workspaceLanguageSettings.isLanguageActive(LanguageClass.TYPESCRIPT)) {
@@ -173,4 +174,13 @@
   public Collection<SectionParser> getSections() {
     return ImmutableList.of(TsConfigRuleSection.PARSER);
   }
+
+  @Nullable
+  @Override
+  public LibrarySource getLibrarySource(BlazeProjectData blazeProjectData) {
+    if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.TYPESCRIPT)) {
+      return null;
+    }
+    return new BlazeTypescriptLibrarySource();
+  }
 }
diff --git a/ijwb/tests/unittests/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteSyncPluginTest.java b/ijwb/tests/unittests/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteSyncPluginTest.java
new file mode 100644
index 0000000..9f3dfd9
--- /dev/null
+++ b/ijwb/tests/unittests/com/google/idea/blaze/ijwb/android/BlazeAndroidLiteSyncPluginTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.ijwb.android;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.idea.blaze.base.BlazeTestCase;
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+import com.google.idea.blaze.base.model.primitives.WorkspaceType;
+import com.google.idea.blaze.base.projectview.ProjectView;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.projectview.section.ListSection;
+import com.google.idea.blaze.base.projectview.section.ScalarSection;
+import com.google.idea.blaze.base.projectview.section.sections.AdditionalLanguagesSection;
+import com.google.idea.blaze.base.projectview.section.sections.WorkspaceTypeSection;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.ErrorCollector;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.google.idea.blaze.base.sync.projectview.LanguageSupport;
+import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
+import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
+import java.util.Set;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link BlazeAndroidLiteSyncPlugin} */
+@RunWith(JUnit4.class)
+public class BlazeAndroidLiteSyncPluginTest extends BlazeTestCase {
+
+  private final ErrorCollector errorCollector = new ErrorCollector();
+  private BlazeContext context;
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+
+    ExtensionPointImpl<BlazeSyncPlugin> ep =
+        registerExtensionPoint(BlazeSyncPlugin.EP_NAME, BlazeSyncPlugin.class);
+    ep.registerExtension(new BlazeAndroidLiteSyncPlugin());
+    // add java, because we need at least one WorkspaceType available.
+    ep.registerExtension(
+        new BlazeSyncPlugin.Adapter() {
+          @Override
+          public ImmutableList<WorkspaceType> getSupportedWorkspaceTypes() {
+            return ImmutableList.of(WorkspaceType.JAVA);
+          }
+
+          @Override
+          public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
+            return ImmutableSet.of(LanguageClass.JAVA);
+          }
+        });
+
+    context = new BlazeContext();
+    context.addOutputSink(IssueOutput.class, errorCollector);
+  }
+
+  @Test
+  public void testAndroidLanguageAvailable() {
+    ProjectViewSet projectViewSet =
+        ProjectViewSet.builder()
+            .add(
+                ProjectView.builder()
+                    .add(ScalarSection.builder(WorkspaceTypeSection.KEY).set(WorkspaceType.JAVA))
+                    .add(
+                        ListSection.builder(AdditionalLanguagesSection.KEY)
+                            .add(LanguageClass.ANDROID))
+                    .build())
+            .build();
+    WorkspaceLanguageSettings workspaceLanguageSettings =
+        LanguageSupport.createWorkspaceLanguageSettings(context, projectViewSet);
+    errorCollector.assertNoIssues();
+    assertThat(workspaceLanguageSettings)
+        .isEqualTo(
+            new WorkspaceLanguageSettings(
+                WorkspaceType.JAVA,
+                ImmutableSet.of(LanguageClass.ANDROID, LanguageClass.GENERIC, LanguageClass.JAVA)));
+  }
+}
diff --git a/ijwb/tests/unittests/com/google/idea/blaze/ijwb/dart/BlazeDartSyncPluginTest.java b/ijwb/tests/unittests/com/google/idea/blaze/ijwb/dart/BlazeDartSyncPluginTest.java
new file mode 100644
index 0000000..2c00838
--- /dev/null
+++ b/ijwb/tests/unittests/com/google/idea/blaze/ijwb/dart/BlazeDartSyncPluginTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.ijwb.dart;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.idea.blaze.base.BlazeTestCase;
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+import com.google.idea.blaze.base.model.primitives.WorkspaceType;
+import com.google.idea.blaze.base.projectview.ProjectView;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.projectview.section.ListSection;
+import com.google.idea.blaze.base.projectview.section.ScalarSection;
+import com.google.idea.blaze.base.projectview.section.sections.AdditionalLanguagesSection;
+import com.google.idea.blaze.base.projectview.section.sections.WorkspaceTypeSection;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.ErrorCollector;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.google.idea.blaze.base.sync.projectview.LanguageSupport;
+import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
+import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
+import java.util.Set;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link BlazeDartSyncPlugin} */
+@RunWith(JUnit4.class)
+public class BlazeDartSyncPluginTest extends BlazeTestCase {
+
+  private final ErrorCollector errorCollector = new ErrorCollector();
+  private BlazeContext context;
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+
+    ExtensionPointImpl<BlazeSyncPlugin> ep =
+        registerExtensionPoint(BlazeSyncPlugin.EP_NAME, BlazeSyncPlugin.class);
+    ep.registerExtension(new BlazeDartSyncPlugin());
+    // add java, because we need at least one WorkspaceType available.
+    ep.registerExtension(
+        new BlazeSyncPlugin.Adapter() {
+          @Override
+          public ImmutableList<WorkspaceType> getSupportedWorkspaceTypes() {
+            return ImmutableList.of(WorkspaceType.JAVA);
+          }
+
+          @Override
+          public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
+            return ImmutableSet.of(LanguageClass.JAVA);
+          }
+        });
+
+    context = new BlazeContext();
+    context.addOutputSink(IssueOutput.class, errorCollector);
+  }
+
+  @Test
+  public void testDartLanguageAvailable() {
+    ProjectViewSet projectViewSet =
+        ProjectViewSet.builder()
+            .add(
+                ProjectView.builder()
+                    .add(ScalarSection.builder(WorkspaceTypeSection.KEY).set(WorkspaceType.JAVA))
+                    .add(
+                        ListSection.builder(AdditionalLanguagesSection.KEY).add(LanguageClass.DART))
+                    .build())
+            .build();
+    WorkspaceLanguageSettings workspaceLanguageSettings =
+        LanguageSupport.createWorkspaceLanguageSettings(context, projectViewSet);
+    errorCollector.assertNoIssues();
+    assertThat(workspaceLanguageSettings)
+        .isEqualTo(
+            new WorkspaceLanguageSettings(
+                WorkspaceType.JAVA,
+                ImmutableSet.of(LanguageClass.DART, LanguageClass.GENERIC, LanguageClass.JAVA)));
+  }
+}
diff --git a/ijwb/tests/unittests/com/google/idea/blaze/ijwb/javascript/BlazeJavascriptSyncPluginTest.java b/ijwb/tests/unittests/com/google/idea/blaze/ijwb/javascript/BlazeJavascriptSyncPluginTest.java
new file mode 100644
index 0000000..b90851e
--- /dev/null
+++ b/ijwb/tests/unittests/com/google/idea/blaze/ijwb/javascript/BlazeJavascriptSyncPluginTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.ijwb.javascript;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.idea.blaze.base.BlazeTestCase;
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+import com.google.idea.blaze.base.model.primitives.WorkspaceType;
+import com.google.idea.blaze.base.projectview.ProjectView;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.projectview.section.ListSection;
+import com.google.idea.blaze.base.projectview.section.ScalarSection;
+import com.google.idea.blaze.base.projectview.section.sections.AdditionalLanguagesSection;
+import com.google.idea.blaze.base.projectview.section.sections.WorkspaceTypeSection;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.ErrorCollector;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.google.idea.blaze.base.sync.projectview.LanguageSupport;
+import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
+import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link BlazeJavascriptSyncPlugin} */
+@RunWith(JUnit4.class)
+public class BlazeJavascriptSyncPluginTest extends BlazeTestCase {
+
+  private final ErrorCollector errorCollector = new ErrorCollector();
+  private BlazeContext context;
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+
+    ExtensionPointImpl<BlazeSyncPlugin> ep =
+        registerExtensionPoint(BlazeSyncPlugin.EP_NAME, BlazeSyncPlugin.class);
+    ep.registerExtension(new BlazeJavascriptSyncPlugin());
+
+    context = new BlazeContext();
+    context.addOutputSink(IssueOutput.class, errorCollector);
+  }
+
+  @Test
+  public void testJavascriptLanguageAvailable() {
+    ProjectViewSet projectViewSet =
+        ProjectViewSet.builder()
+            .add(
+                ProjectView.builder()
+                    .add(
+                        ScalarSection.builder(WorkspaceTypeSection.KEY)
+                            .set(WorkspaceType.JAVASCRIPT))
+                    .add(
+                        ListSection.builder(AdditionalLanguagesSection.KEY)
+                            .add(LanguageClass.JAVASCRIPT))
+                    .build())
+            .build();
+    WorkspaceLanguageSettings workspaceLanguageSettings =
+        LanguageSupport.createWorkspaceLanguageSettings(context, projectViewSet);
+    errorCollector.assertNoIssues();
+    assertThat(workspaceLanguageSettings)
+        .isEqualTo(
+            new WorkspaceLanguageSettings(
+                WorkspaceType.JAVASCRIPT,
+                ImmutableSet.of(LanguageClass.JAVASCRIPT, LanguageClass.GENERIC)));
+  }
+}
diff --git a/ijwb/tests/unittests/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptSyncPluginTest.java b/ijwb/tests/unittests/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptSyncPluginTest.java
new file mode 100644
index 0000000..e39fa49
--- /dev/null
+++ b/ijwb/tests/unittests/com/google/idea/blaze/ijwb/typescript/BlazeTypescriptSyncPluginTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.ijwb.typescript;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.idea.blaze.base.BlazeTestCase;
+import com.google.idea.blaze.base.model.primitives.LanguageClass;
+import com.google.idea.blaze.base.model.primitives.WorkspaceType;
+import com.google.idea.blaze.base.projectview.ProjectView;
+import com.google.idea.blaze.base.projectview.ProjectViewSet;
+import com.google.idea.blaze.base.projectview.section.ListSection;
+import com.google.idea.blaze.base.projectview.section.ScalarSection;
+import com.google.idea.blaze.base.projectview.section.sections.AdditionalLanguagesSection;
+import com.google.idea.blaze.base.projectview.section.sections.WorkspaceTypeSection;
+import com.google.idea.blaze.base.scope.BlazeContext;
+import com.google.idea.blaze.base.scope.ErrorCollector;
+import com.google.idea.blaze.base.scope.output.IssueOutput;
+import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.google.idea.blaze.base.sync.projectview.LanguageSupport;
+import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
+import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
+import java.util.Set;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link BlazeTypescriptSyncPlugin} */
+@RunWith(JUnit4.class)
+public class BlazeTypescriptSyncPluginTest extends BlazeTestCase {
+
+  private final ErrorCollector errorCollector = new ErrorCollector();
+  private BlazeContext context;
+
+  @Override
+  protected void initTest(
+      @NotNull Container applicationServices, @NotNull Container projectServices) {
+    super.initTest(applicationServices, projectServices);
+
+    ExtensionPointImpl<BlazeSyncPlugin> ep =
+        registerExtensionPoint(BlazeSyncPlugin.EP_NAME, BlazeSyncPlugin.class);
+    ep.registerExtension(new BlazeTypescriptSyncPlugin());
+    // add java, because we need at least one WorkspaceType available.
+    ep.registerExtension(
+        new BlazeSyncPlugin.Adapter() {
+          @Override
+          public ImmutableList<WorkspaceType> getSupportedWorkspaceTypes() {
+            return ImmutableList.of(WorkspaceType.JAVA);
+          }
+
+          @Override
+          public Set<LanguageClass> getSupportedLanguagesInWorkspace(WorkspaceType workspaceType) {
+            return ImmutableSet.of(LanguageClass.JAVA);
+          }
+        });
+
+    context = new BlazeContext();
+    context.addOutputSink(IssueOutput.class, errorCollector);
+  }
+
+  @Test
+  public void testTypescriptLanguageAvailable() {
+    ProjectViewSet projectViewSet =
+        ProjectViewSet.builder()
+            .add(
+                ProjectView.builder()
+                    .add(ScalarSection.builder(WorkspaceTypeSection.KEY).set(WorkspaceType.JAVA))
+                    .add(
+                        ListSection.builder(AdditionalLanguagesSection.KEY)
+                            .add(LanguageClass.TYPESCRIPT))
+                    .build())
+            .build();
+    WorkspaceLanguageSettings workspaceLanguageSettings =
+        LanguageSupport.createWorkspaceLanguageSettings(context, projectViewSet);
+    errorCollector.assertNoIssues();
+    assertThat(workspaceLanguageSettings)
+        .isEqualTo(
+            new WorkspaceLanguageSettings(
+                WorkspaceType.JAVA,
+                ImmutableSet.of(
+                    LanguageClass.TYPESCRIPT, LanguageClass.GENERIC, LanguageClass.JAVA)));
+  }
+}
diff --git a/intellij_platform_sdk/BUILD b/intellij_platform_sdk/BUILD
index a733a02..a0aad73 100644
--- a/intellij_platform_sdk/BUILD
+++ b/intellij_platform_sdk/BUILD
@@ -59,6 +59,17 @@
     ],
 )
 
+java_library(
+    name = "junit",
+    neverlink = 1,
+    exports = select({
+        ":intellij-latest": ["@intellij_latest//:junit"],
+        ":android-studio-latest": ["@android_studio_latest//:junit"],
+        ":clion-latest": [],
+        "//conditions:default": ["@intellij_latest//:junit"],
+    }),
+)
+
 # The dev kit is only for IntelliJ since you only develop plugins in Java.
 java_library(
     name = "devkit",
diff --git a/intellij_platform_sdk/BUILD.android_studio b/intellij_platform_sdk/BUILD.android_studio
index 00edce8..77c0f55 100644
--- a/intellij_platform_sdk/BUILD.android_studio
+++ b/intellij_platform_sdk/BUILD.android_studio
@@ -17,9 +17,15 @@
     jars = glob([
         "android-studio/plugins/android/lib/*.jar",
         "android-studio/plugins/android-ndk/lib/*.jar",
+        "android-studio/plugins/sdk-updates/lib/*.jar",
     ]),
 )
 
+java_import(
+    name = "junit",
+    jars = glob(["android-studio/plugins/junit/lib/*.jar"]),
+)
+
 # The plugins required by ASwB. We need to include them
 # when running integration tests.
 java_import(
diff --git a/intellij_platform_sdk/BUILD.idea b/intellij_platform_sdk/BUILD.idea
index 251b620..22082d2 100644
--- a/intellij_platform_sdk/BUILD.idea
+++ b/intellij_platform_sdk/BUILD.idea
@@ -15,6 +15,11 @@
     jars = glob(["idea-IC-*/plugins/devkit/lib/devkit.jar"]),
 )
 
+java_import(
+    name = "junit",
+    jars = glob(["idea-IC-*/plugins/junit/lib/*.jar"]),
+)
+
 # The plugins required by IJwB. We need to include them
 # when running integration tests.
 java_import(
diff --git a/java/src/META-INF/blaze-java.xml b/java/src/META-INF/blaze-java.xml
index 937cb85..ae7ca4d 100644
--- a/java/src/META-INF/blaze-java.xml
+++ b/java/src/META-INF/blaze-java.xml
@@ -30,10 +30,10 @@
             text="Attach Source Jar">
       <add-to-group group-id="Blaze.ProjectViewPopupMenu"/>
     </action>
-    <action class="com.google.idea.blaze.java.libraries.AddLibraryRuleDirectoryToProjectViewAction"
-            id="Blaze.AddLibraryRuleDirectoryToProjectView"
+    <action class="com.google.idea.blaze.java.libraries.AddLibraryTargetDirectoryToProjectViewAction"
+            id="Blaze.AddLibraryTargetDirectoryToProjectView"
             icon="BlazeIcons.Blaze"
-            text="Add Library Rule Directory To Project View">
+            text="Add Library Target Directory To Project View">
       <add-to-group group-id="Blaze.ProjectViewPopupMenu"/>
     </action>
 
@@ -94,7 +94,7 @@
                                      implementation="com.google.idea.blaze.java.lang.build.BuildFileSafeDeleteProcessor"/>
     <projectService serviceImplementation="com.google.idea.blaze.java.libraries.JarCache"/>
 
-    <attachSourcesProvider implementation="com.google.idea.blaze.java.libraries.AddLibraryRuleDirectoryToProjectViewAttachSourcesProvider"/>
+    <attachSourcesProvider implementation="com.google.idea.blaze.java.libraries.AddLibraryTargetDirectoryToProjectViewAttachSourcesProvider"/>
     <attachSourcesProvider implementation="com.google.idea.blaze.java.libraries.BlazeAttachSourceProvider"/>
     <applicationService serviceImplementation="com.google.idea.blaze.java.settings.BlazeJavaUserSettings"/>
     <projectService serviceImplementation="com.google.idea.blaze.java.syncstatus.SyncStatusHelper"/>
diff --git a/java/src/com/google/idea/blaze/java/libraries/AddLibraryRuleDirectoryToProjectViewAction.java b/java/src/com/google/idea/blaze/java/libraries/AddLibraryTargetDirectoryToProjectViewAction.java
similarity index 89%
rename from java/src/com/google/idea/blaze/java/libraries/AddLibraryRuleDirectoryToProjectViewAction.java
rename to java/src/com/google/idea/blaze/java/libraries/AddLibraryTargetDirectoryToProjectViewAction.java
index 1bf26e7..935fded 100644
--- a/java/src/com/google/idea/blaze/java/libraries/AddLibraryRuleDirectoryToProjectViewAction.java
+++ b/java/src/com/google/idea/blaze/java/libraries/AddLibraryTargetDirectoryToProjectViewAction.java
@@ -15,11 +15,12 @@
  */
 package com.google.idea.blaze.java.libraries;
 
+import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import com.google.idea.blaze.base.actions.BlazeAction;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.model.primitives.WorkspacePath;
@@ -46,7 +47,7 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-class AddLibraryRuleDirectoryToProjectViewAction extends BlazeAction {
+class AddLibraryTargetDirectoryToProjectViewAction extends BlazeAction {
   @Override
   public void actionPerformed(AnActionEvent e) {
     Project project = e.getProject();
@@ -88,24 +89,24 @@
     if (blazeLibrary == null) {
       return null;
     }
-    RuleKey originatingRule = blazeLibrary.originatingRule;
-    if (originatingRule == null) {
+    TargetKey originatingTarget = blazeLibrary.originatingTarget;
+    if (originatingTarget == null) {
       return null;
     }
-    RuleIdeInfo rule = blazeProjectData.ruleMap.get(originatingRule);
-    if (rule == null) {
+    TargetIdeInfo target = blazeProjectData.targetMap.get(originatingTarget);
+    if (target == null) {
       return null;
     }
     // To start with, we whitelist only library rules
     // It makes no sense to add directories for java_imports and the like
-    if (!rule.kind.isOneOf(Kind.JAVA_LIBRARY, Kind.ANDROID_LIBRARY)) {
+    if (!target.kind.isOneOf(Kind.JAVA_LIBRARY, Kind.ANDROID_LIBRARY)) {
       return null;
     }
-    if (rule.buildFile == null) {
+    if (target.buildFile == null) {
       return null;
     }
-    File buildFile = new File(rule.buildFile.getRelativePath());
-    WorkspacePath workspacePath = new WorkspacePath(buildFile.getParent());
+    File buildFile = new File(target.buildFile.getRelativePath());
+    WorkspacePath workspacePath = new WorkspacePath(Strings.nullToEmpty(buildFile.getParent()));
     ProjectViewSet projectViewSet = ProjectViewManager.getInstance(project).getProjectViewSet();
     if (projectViewSet == null) {
       return null;
diff --git a/java/src/com/google/idea/blaze/java/libraries/AddLibraryRuleDirectoryToProjectViewAttachSourcesProvider.java b/java/src/com/google/idea/blaze/java/libraries/AddLibraryTargetDirectoryToProjectViewAttachSourcesProvider.java
similarity index 90%
rename from java/src/com/google/idea/blaze/java/libraries/AddLibraryRuleDirectoryToProjectViewAttachSourcesProvider.java
rename to java/src/com/google/idea/blaze/java/libraries/AddLibraryTargetDirectoryToProjectViewAttachSourcesProvider.java
index 03aaf3b..a12fd46 100644
--- a/java/src/com/google/idea/blaze/java/libraries/AddLibraryRuleDirectoryToProjectViewAttachSourcesProvider.java
+++ b/java/src/com/google/idea/blaze/java/libraries/AddLibraryTargetDirectoryToProjectViewAttachSourcesProvider.java
@@ -30,8 +30,8 @@
 import java.util.List;
 import org.jetbrains.annotations.NotNull;
 
-/** @author Sergey Evdokimov */
-public class AddLibraryRuleDirectoryToProjectViewAttachSourcesProvider
+/** */
+public class AddLibraryTargetDirectoryToProjectViewAttachSourcesProvider
     implements AttachSourcesProvider {
 
   @NotNull
@@ -49,7 +49,8 @@
     for (LibraryOrderEntry orderEntry : orderEntries) {
       Library library = orderEntry.getLibrary();
       WorkspacePath workspacePath =
-          AddLibraryRuleDirectoryToProjectViewAction.getDirectoryToAddForLibrary(project, library);
+          AddLibraryTargetDirectoryToProjectViewAction.getDirectoryToAddForLibrary(
+              project, library);
       if (workspacePath == null) {
         continue;
       }
@@ -74,7 +75,7 @@
 
           @Override
           public ActionCallback perform(List<LibraryOrderEntry> orderEntriesContainingFile) {
-            AddLibraryRuleDirectoryToProjectViewAction.addDirectoriesToProjectView(
+            AddLibraryTargetDirectoryToProjectViewAction.addDirectoriesToProjectView(
                 project, librariesToAttachSourceTo);
             return ActionCallback.DONE;
           }
diff --git a/java/src/com/google/idea/blaze/java/libraries/AttachSourceJarAction.java b/java/src/com/google/idea/blaze/java/libraries/AttachSourceJarAction.java
index d6daa4f..7e18d28 100644
--- a/java/src/com/google/idea/blaze/java/libraries/AttachSourceJarAction.java
+++ b/java/src/com/google/idea/blaze/java/libraries/AttachSourceJarAction.java
@@ -19,8 +19,8 @@
 import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.base.sync.libraries.LibraryEditor;
 import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
-import com.google.idea.blaze.java.sync.projectstructure.LibraryEditor;
 import com.intellij.CommonBundle;
 import com.intellij.openapi.actionSystem.AnActionEvent;
 import com.intellij.openapi.actionSystem.Presentation;
diff --git a/java/src/com/google/idea/blaze/java/libraries/BlazeAttachSourceProvider.java b/java/src/com/google/idea/blaze/java/libraries/BlazeAttachSourceProvider.java
index 04e0d8c..05759ba 100644
--- a/java/src/com/google/idea/blaze/java/libraries/BlazeAttachSourceProvider.java
+++ b/java/src/com/google/idea/blaze/java/libraries/BlazeAttachSourceProvider.java
@@ -19,13 +19,13 @@
 import com.google.common.collect.Lists;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
 import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
+import com.google.idea.blaze.base.model.BlazeLibrary;
 import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.LibraryKey;
 import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.base.sync.libraries.LibraryEditor;
 import com.google.idea.blaze.java.settings.BlazeJavaUserSettings;
 import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
-import com.google.idea.blaze.java.sync.model.BlazeLibrary;
-import com.google.idea.blaze.java.sync.model.LibraryKey;
-import com.google.idea.blaze.java.sync.projectstructure.LibraryEditor;
 import com.intellij.codeInsight.AttachSourcesProvider;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.project.Project;
diff --git a/java/src/com/google/idea/blaze/java/libraries/DetachAllSourceJarsAction.java b/java/src/com/google/idea/blaze/java/libraries/DetachAllSourceJarsAction.java
index f30518a..10f70a3 100644
--- a/java/src/com/google/idea/blaze/java/libraries/DetachAllSourceJarsAction.java
+++ b/java/src/com/google/idea/blaze/java/libraries/DetachAllSourceJarsAction.java
@@ -18,10 +18,10 @@
 import com.google.common.collect.Lists;
 import com.google.idea.blaze.base.actions.BlazeAction;
 import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.LibraryKey;
 import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.base.sync.libraries.LibraryEditor;
 import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
-import com.google.idea.blaze.java.sync.model.LibraryKey;
-import com.google.idea.blaze.java.sync.projectstructure.LibraryEditor;
 import com.intellij.openapi.actionSystem.AnActionEvent;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.project.Project;
diff --git a/java/src/com/google/idea/blaze/java/libraries/JarCache.java b/java/src/com/google/idea/blaze/java/libraries/JarCache.java
index b0a8ec1..544e7ad 100644
--- a/java/src/com/google/idea/blaze/java/libraries/JarCache.java
+++ b/java/src/com/google/idea/blaze/java/libraries/JarCache.java
@@ -25,6 +25,7 @@
 import com.google.idea.blaze.base.filecache.FileCache;
 import com.google.idea.blaze.base.filecache.FileDiffer;
 import com.google.idea.blaze.base.io.FileSizeScanner;
+import com.google.idea.blaze.base.model.BlazeLibrary;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.prefetch.FetchExecutor;
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
@@ -33,13 +34,12 @@
 import com.google.idea.blaze.base.settings.BlazeImportSettings;
 import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
 import com.google.idea.blaze.base.sync.BlazeSyncParams;
+import com.google.idea.blaze.base.sync.BlazeSyncParams.SyncMode;
 import com.google.idea.blaze.base.sync.data.BlazeDataStorage;
+import com.google.idea.blaze.base.sync.libraries.BlazeLibraryCollector;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
 import com.google.idea.blaze.java.settings.BlazeJavaUserSettings;
-import com.google.idea.blaze.java.sync.BlazeLibraryCollector;
 import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
-import com.google.idea.blaze.java.sync.model.BlazeLibrary;
-import com.google.idea.common.experiments.BoolExperiment;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.diagnostic.Logger;
@@ -60,8 +60,6 @@
 /** Local cache of the jars referenced by the project. */
 public class JarCache {
   private static final Logger LOG = Logger.getInstance(JarCache.class);
-  public static final BoolExperiment ENABLE_JAR_CACHE =
-      new BoolExperiment("enable.jar.cache", true);
 
   private final Project project;
   private final BlazeImportSettings importSettings;
@@ -82,7 +80,8 @@
   public void onSync(
       BlazeContext context, BlazeProjectData projectData, BlazeSyncParams.SyncMode syncMode) {
     Collection<BlazeLibrary> libraries = BlazeLibraryCollector.getLibraries(projectData);
-    boolean fullRefresh = syncMode == BlazeSyncParams.SyncMode.FULL;
+    boolean fullRefresh = syncMode == SyncMode.FULL;
+    boolean removeMissingFiles = syncMode == SyncMode.INCREMENTAL;
     boolean enabled = updateEnabled();
 
     if (!enabled || fullRefresh) {
@@ -118,7 +117,7 @@
     }
 
     this.sourceFileToCacheKey = sourceFileToCacheKey;
-    refresh(context, true);
+    refresh(context, removeMissingFiles);
   }
 
   public boolean isEnabled() {
@@ -128,7 +127,6 @@
   private boolean updateEnabled() {
     this.enabled =
         BlazeJavaUserSettings.getInstance().getUseJarCache()
-            && ENABLE_JAR_CACHE.getValue()
             && !ApplicationManager.getApplication().isUnitTestMode();
     return enabled;
   }
diff --git a/java/src/com/google/idea/blaze/java/libraries/LibraryActionHelper.java b/java/src/com/google/idea/blaze/java/libraries/LibraryActionHelper.java
index 7e7db55..35a00ca 100644
--- a/java/src/com/google/idea/blaze/java/libraries/LibraryActionHelper.java
+++ b/java/src/com/google/idea/blaze/java/libraries/LibraryActionHelper.java
@@ -15,11 +15,11 @@
  */
 package com.google.idea.blaze.java.libraries;
 
+import com.google.idea.blaze.base.model.BlazeLibrary;
 import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.LibraryKey;
 import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
 import com.google.idea.blaze.java.sync.model.BlazeJavaSyncData;
-import com.google.idea.blaze.java.sync.model.BlazeLibrary;
-import com.google.idea.blaze.java.sync.model.LibraryKey;
 import com.intellij.ide.projectView.impl.nodes.NamedLibraryElementNode;
 import com.intellij.openapi.actionSystem.AnActionEvent;
 import com.intellij.openapi.actionSystem.CommonDataKeys;
diff --git a/java/src/com/google/idea/blaze/java/libraries/SourceJarManager.java b/java/src/com/google/idea/blaze/java/libraries/SourceJarManager.java
index 10ea861..c00c5b0 100644
--- a/java/src/com/google/idea/blaze/java/libraries/SourceJarManager.java
+++ b/java/src/com/google/idea/blaze/java/libraries/SourceJarManager.java
@@ -16,7 +16,7 @@
 package com.google.idea.blaze.java.libraries;
 
 import com.google.common.collect.Sets;
-import com.google.idea.blaze.java.sync.model.LibraryKey;
+import com.google.idea.blaze.base.model.LibraryKey;
 import com.intellij.openapi.components.PersistentStateComponent;
 import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.components.State;
diff --git a/java/src/com/google/idea/blaze/java/run/BlazeJavaRunConfigurationFactory.java b/java/src/com/google/idea/blaze/java/run/BlazeJavaRunConfigurationFactory.java
index eb2c0d2..3b7c1b5 100644
--- a/java/src/com/google/idea/blaze/java/run/BlazeJavaRunConfigurationFactory.java
+++ b/java/src/com/google/idea/blaze/java/run/BlazeJavaRunConfigurationFactory.java
@@ -16,8 +16,8 @@
 package com.google.idea.blaze.java.run;
 
 import com.google.idea.blaze.base.command.BlazeCommandName;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.model.primitives.Label;
@@ -32,9 +32,9 @@
 /** Creates run configurations for java_binary. */
 public class BlazeJavaRunConfigurationFactory extends BlazeRunConfigurationFactory {
   @Override
-  public boolean handlesTarget(Project project, BlazeProjectData blazeProjectData, Label target) {
-    RuleIdeInfo rule = blazeProjectData.ruleMap.get(RuleKey.forPlainTarget(target));
-    return rule != null && rule.kind == Kind.JAVA_BINARY;
+  public boolean handlesTarget(Project project, BlazeProjectData blazeProjectData, Label label) {
+    TargetIdeInfo target = blazeProjectData.targetMap.get(TargetKey.forPlainTarget(label));
+    return target != null && target.kind == Kind.JAVA_BINARY;
   }
 
   @Override
diff --git a/java/src/com/google/idea/blaze/java/run/BlazeJavaTestRunConfigurationFactory.java b/java/src/com/google/idea/blaze/java/run/BlazeJavaTestRunConfigurationFactory.java
index 0722e86..007aba3 100644
--- a/java/src/com/google/idea/blaze/java/run/BlazeJavaTestRunConfigurationFactory.java
+++ b/java/src/com/google/idea/blaze/java/run/BlazeJavaTestRunConfigurationFactory.java
@@ -16,8 +16,8 @@
 package com.google.idea.blaze.java.run;
 
 import com.google.idea.blaze.base.command.BlazeCommandName;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.model.primitives.Label;
@@ -32,9 +32,9 @@
 /** Creates run configurations for java_test and android_robolectric_test. */
 public class BlazeJavaTestRunConfigurationFactory extends BlazeRunConfigurationFactory {
   @Override
-  public boolean handlesTarget(Project project, BlazeProjectData blazeProjectData, Label target) {
-    RuleIdeInfo rule = blazeProjectData.ruleMap.get(RuleKey.forPlainTarget(target));
-    return rule != null && rule.kindIsOneOf(Kind.JAVA_TEST, Kind.ANDROID_ROBOLECTRIC_TEST);
+  public boolean handlesTarget(Project project, BlazeProjectData blazeProjectData, Label label) {
+    TargetIdeInfo target = blazeProjectData.targetMap.get(TargetKey.forPlainTarget(label));
+    return target != null && target.kindIsOneOf(Kind.JAVA_TEST, Kind.ANDROID_ROBOLECTRIC_TEST);
   }
 
   @Override
diff --git a/java/src/com/google/idea/blaze/java/run/RunUtil.java b/java/src/com/google/idea/blaze/java/run/RunUtil.java
index fab9497..724d72f 100644
--- a/java/src/com/google/idea/blaze/java/run/RunUtil.java
+++ b/java/src/com/google/idea/blaze/java/run/RunUtil.java
@@ -15,12 +15,12 @@
  */
 package com.google.idea.blaze.java.run;
 
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.ideinfo.TestIdeInfo;
 import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.run.TestRuleFinder;
-import com.google.idea.blaze.base.run.TestRuleHeuristic;
-import com.google.idea.blaze.base.run.rulefinder.RuleFinder;
+import com.google.idea.blaze.base.run.TestTargetFinder;
+import com.google.idea.blaze.base.run.TestTargetHeuristic;
+import com.google.idea.blaze.base.run.targetfinder.TargetFinder;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiClass;
@@ -40,7 +40,7 @@
    *     containing rules, the first rule sorted alphabetically by label.
    */
   @Nullable
-  public static RuleIdeInfo ruleForTestClass(
+  public static TargetIdeInfo targetForTestClass(
       @NotNull Project project,
       @NotNull PsiClass testClass,
       @Nullable TestIdeInfo.TestSize testSize) {
@@ -48,13 +48,14 @@
     if (testFile == null) {
       return null;
     }
-    Collection<RuleIdeInfo> rules =
-        TestRuleFinder.getInstance(project).testTargetsForSourceFile(testFile);
-    Label testLabel = TestRuleHeuristic.chooseTestTargetForSourceFile(testFile, rules, testSize);
+    Collection<TargetIdeInfo> targets =
+        TestTargetFinder.getInstance(project).testTargetsForSourceFile(testFile);
+    Label testLabel =
+        TestTargetHeuristic.chooseTestTargetForSourceFile(testFile, targets, testSize);
     if (testLabel == null) {
       return null;
     }
-    return RuleFinder.getInstance().ruleForTarget(project, testLabel);
+    return TargetFinder.getInstance().targetForLabel(project, testLabel);
   }
 
   /**
diff --git a/java/src/com/google/idea/blaze/java/run/producers/BlazeJUnitTestFilterFlags.java b/java/src/com/google/idea/blaze/java/run/producers/BlazeJUnitTestFilterFlags.java
index 1c6fef8..2c6a6aa 100644
--- a/java/src/com/google/idea/blaze/java/run/producers/BlazeJUnitTestFilterFlags.java
+++ b/java/src/com/google/idea/blaze/java/run/producers/BlazeJUnitTestFilterFlags.java
@@ -18,12 +18,16 @@
 
 import com.google.common.base.Strings;
 import com.google.idea.blaze.base.command.BlazeFlags;
+import com.google.idea.common.experiments.BoolExperiment;
 import java.util.Collection;
 import javax.annotation.Nullable;
 
 /** Utilities for building test filter flags for JUnit tests. */
 public final class BlazeJUnitTestFilterFlags {
 
+  private static final BoolExperiment enableParameterizedSupport =
+      new BoolExperiment("enable.parameterized.test.support", true);
+
   /** A version of JUnit to generate test filter flags for. */
   public enum JUnitVersion {
     JUNIT_3,
@@ -31,11 +35,15 @@
   }
 
   public static String testFilterFlagForClass(String className, JUnitVersion jUnitVersion) {
-    return testFilterFlagForClassAndMethod(className, null, jUnitVersion);
+    return testFilterFlagForClassAndMethod(
+        className, null, jUnitVersion, /* parameterized doesn't matter for a class */ false);
   }
 
   public static String testFilterFlagForClassAndMethod(
-      String className, @Nullable String methodName, JUnitVersion jUnitVersion) {
+      String className,
+      @Nullable String methodName,
+      JUnitVersion jUnitVersion,
+      boolean parameterized) {
     StringBuilder output = new StringBuilder(BlazeFlags.TEST_FILTER);
     output.append('=');
     output.append(className);
@@ -46,7 +54,11 @@
       // JUnit 4 test filters are regexes, and must be terminated to avoid matching
       // unintended classes/methods. JUnit 3 test filters do not need or support this syntax.
       if (jUnitVersion == JUnitVersion.JUNIT_4) {
-        output.append('$');
+        // parameterized tests include their parameters between brackets after the method name
+        if (parameterized && enableParameterizedSupport.getValue()) {
+          output.append("(\\[.+\\])?");
+        }
+        output.append("$");
       }
     } else if (jUnitVersion == JUnitVersion.JUNIT_4) {
       output.append('#');
@@ -56,12 +68,15 @@
   }
 
   public static String testFilterFlagForClassAndMethods(
-      String className, Collection<String> methodNames, JUnitVersion jUnitVersion) {
+      String className,
+      Collection<String> methodNames,
+      JUnitVersion jUnitVersion,
+      boolean parameterized) {
     if (methodNames.size() == 0) {
       return testFilterFlagForClass(className, jUnitVersion);
     } else if (methodNames.size() == 1) {
       return testFilterFlagForClassAndMethod(
-          className, methodNames.iterator().next(), jUnitVersion);
+          className, methodNames.iterator().next(), jUnitVersion, parameterized);
     }
     String methodNamePattern;
     if (jUnitVersion == JUnitVersion.JUNIT_4) {
@@ -69,7 +84,8 @@
     } else {
       methodNamePattern = String.join(",", methodNames);
     }
-    return testFilterFlagForClassAndMethod(className, methodNamePattern, jUnitVersion);
+    return testFilterFlagForClassAndMethod(
+        className, methodNamePattern, jUnitVersion, parameterized);
   }
 
   private BlazeJUnitTestFilterFlags() {}
diff --git a/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaMainClassRunConfigurationProducer.java b/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaMainClassRunConfigurationProducer.java
index 43ba6d0..6e9be07 100644
--- a/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaMainClassRunConfigurationProducer.java
+++ b/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaMainClassRunConfigurationProducer.java
@@ -16,18 +16,19 @@
 package com.google.idea.blaze.java.run.producers;
 
 import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.Sets;
 import com.google.idea.blaze.base.command.BlazeCommandName;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.model.primitives.Label;
-import com.google.idea.blaze.base.rulemaps.SourceToRuleMap;
 import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
 import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
 import com.google.idea.blaze.base.run.producers.BlazeRunConfigurationProducer;
 import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState;
 import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
+import com.google.idea.blaze.base.targetmaps.SourceToTargetMap;
 import com.google.idea.blaze.java.run.RunUtil;
 import com.intellij.execution.JavaExecutionUtil;
 import com.intellij.execution.Location;
@@ -40,9 +41,10 @@
 import com.intellij.psi.PsiMethod;
 import com.intellij.psi.util.PsiMethodUtil;
 import java.io.File;
-import java.util.List;
+import java.util.ArrayDeque;
 import java.util.Objects;
-import java.util.stream.Collectors;
+import java.util.Queue;
+import java.util.Set;
 import org.jetbrains.annotations.Nullable;
 
 /** Creates run configurations for Java main classes sourced by java_binary targets. */
@@ -71,7 +73,7 @@
       sourceElement.set(mainMethod);
     }
 
-    Label label = getRuleLabel(context.getProject(), mainClass);
+    Label label = getTargetLabel(context.getProject(), mainClass);
     if (label == null) {
       return false;
     }
@@ -101,7 +103,7 @@
     if (mainClass == null) {
       return false;
     }
-    Label label = getRuleLabel(context.getProject(), mainClass);
+    Label label = getTargetLabel(context.getProject(), mainClass);
     if (label == null) {
       return false;
     }
@@ -126,28 +128,34 @@
   }
 
   @Nullable
-  private static Label getRuleLabel(Project project, PsiClass mainClass) {
+  private static Label getTargetLabel(Project project, PsiClass mainClass) {
     File mainClassFile = RunUtil.getFileForClass(mainClass);
-    ImmutableCollection<RuleKey> ruleKeys =
-        SourceToRuleMap.getInstance(project).getRulesForSourceFile(mainClassFile);
+    ImmutableCollection<TargetKey> targetKeys =
+        SourceToTargetMap.getInstance(project).getRulesForSourceFile(mainClassFile);
     BlazeProjectData blazeProjectData =
         BlazeProjectDataManager.getInstance(project).getBlazeProjectData();
     if (blazeProjectData == null) {
       return null;
     }
-    List<RuleIdeInfo> rules =
-        ruleKeys
-            .stream()
-            .map(blazeProjectData.ruleMap::get)
-            .filter(Objects::nonNull)
-            .filter(RuleIdeInfo::isPlainTarget)
-            .collect(Collectors.toList());
-    for (RuleIdeInfo rule : rules) {
-      if (rule.kind == Kind.JAVA_BINARY) {
+    // Find the first java_binary, BFS
+    Queue<TargetKey> todo = new ArrayDeque<>();
+    todo.addAll(targetKeys);
+    Set<TargetKey> seen = Sets.newHashSet();
+    while (!todo.isEmpty()) {
+      TargetKey targetKey = todo.remove();
+      if (!seen.add(targetKey)) {
+        continue;
+      }
+      TargetIdeInfo target = blazeProjectData.targetMap.get(targetKey);
+      if (target == null) {
+        continue;
+      }
+      if (target.kind == Kind.JAVA_BINARY && target.isPlainTarget()) {
         // Best-effort guess: the main_class attribute isn't exposed, but assume
         // mainClass is the main_class because it is sourced by the java_binary.
-        return rule.label;
+        return target.key.label;
       }
+      todo.addAll(blazeProjectData.reverseDependencies.get(targetKey));
     }
     return null;
   }
diff --git a/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducer.java b/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducer.java
index 6a92166..3eb8ab3 100644
--- a/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducer.java
+++ b/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestClassConfigurationProducer.java
@@ -18,7 +18,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.idea.blaze.base.command.BlazeCommandName;
 import com.google.idea.blaze.base.command.BlazeFlags;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.ideinfo.TestIdeInfo;
 import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
 import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
@@ -75,12 +75,12 @@
     sourceElement.set(testClass);
 
     TestIdeInfo.TestSize testSize = TestSizeAnnotationMap.getTestSize(testClass);
-    RuleIdeInfo rule = RunUtil.ruleForTestClass(context.getProject(), testClass, testSize);
-    if (rule == null) {
+    TargetIdeInfo target = RunUtil.targetForTestClass(context.getProject(), testClass, testSize);
+    if (target == null) {
       return false;
     }
 
-    configuration.setTarget(rule.label);
+    configuration.setTarget(target.key.label);
     BlazeCommandRunConfigurationCommonState handlerState =
         configuration.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
     if (handlerState == null) {
diff --git a/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducer.java b/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducer.java
index 419f979..2fc899f 100644
--- a/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducer.java
+++ b/java/src/com/google/idea/blaze/java/run/producers/BlazeJavaTestMethodConfigurationProducer.java
@@ -18,7 +18,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.idea.blaze.base.command.BlazeCommandName;
 import com.google.idea.blaze.base.command.BlazeFlags;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.ideinfo.TestIdeInfo;
 import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration;
 import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
@@ -29,6 +29,7 @@
 import com.google.idea.blaze.java.run.producers.BlazeJUnitTestFilterFlags.JUnitVersion;
 import com.intellij.execution.actions.ConfigurationContext;
 import com.intellij.execution.junit.JUnitUtil;
+import com.intellij.execution.junit2.PsiMemberParameterizedLocation;
 import com.intellij.openapi.util.Ref;
 import com.intellij.psi.PsiClass;
 import com.intellij.psi.PsiElement;
@@ -82,13 +83,13 @@
     sourceElement.set(methodInfo.firstMethod);
 
     TestIdeInfo.TestSize testSize = TestSizeAnnotationMap.getTestSize(methodInfo.firstMethod);
-    RuleIdeInfo rule =
-        RunUtil.ruleForTestClass(context.getProject(), methodInfo.containingClass, testSize);
-    if (rule == null) {
+    TargetIdeInfo target =
+        RunUtil.targetForTestClass(context.getProject(), methodInfo.containingClass, testSize);
+    if (target == null) {
       return false;
     }
 
-    configuration.setTarget(rule.label);
+    configuration.setTarget(target.key.label);
     BlazeCommandRunConfigurationCommonState handlerState =
         configuration.getHandlerStateIfType(BlazeCommandRunConfigurationCommonState.class);
     if (handlerState == null) {
@@ -165,10 +166,15 @@
     }
     final JUnitVersion jUnitVersion =
         JUnitUtil.isJUnit4TestClass(containingClass) ? JUnitVersion.JUNIT_4 : JUnitVersion.JUNIT_3;
+    boolean parameterized = isParameterized(containingClass);
     final String testFilterFlag =
         BlazeJUnitTestFilterFlags.testFilterFlagForClassAndMethods(
-            qualifiedName, methodNames, jUnitVersion);
+            qualifiedName, methodNames, jUnitVersion, parameterized);
 
     return new SelectedMethodInfo(firstMethod, containingClass, methodNames, testFilterFlag);
   }
+
+  private static boolean isParameterized(PsiClass testClass) {
+    return PsiMemberParameterizedLocation.getParameterizedLocation(testClass, null) != null;
+  }
 }
diff --git a/java/src/com/google/idea/blaze/java/settings/BlazeJavaUserSettingsContributor.java b/java/src/com/google/idea/blaze/java/settings/BlazeJavaUserSettingsContributor.java
index bf5bad9..cd517e1 100644
--- a/java/src/com/google/idea/blaze/java/settings/BlazeJavaUserSettingsContributor.java
+++ b/java/src/com/google/idea/blaze/java/settings/BlazeJavaUserSettingsContributor.java
@@ -19,7 +19,6 @@
 import com.google.common.collect.ImmutableList;
 import com.google.idea.blaze.base.settings.Blaze;
 import com.google.idea.blaze.base.settings.ui.BlazeUserSettingsContributor;
-import com.google.idea.blaze.java.libraries.JarCache;
 import com.intellij.openapi.ui.Messages;
 import com.intellij.uiDesigner.core.GridConstraints;
 import javax.swing.JCheckBox;
@@ -67,12 +66,7 @@
     attachSourcesOnDemand.setSelected(false);
     attachSourcesOnDemand.setText("Automatically attach sources when you open decompiled source");
 
-    ImmutableList.Builder<JComponent> builder = ImmutableList.builder();
-    if (JarCache.ENABLE_JAR_CACHE.getValue()) {
-      builder.add(useJarCache);
-    }
-    builder.add(attachSourcesOnDemand).add(attachSourcesByDefault);
-    components = builder.build();
+    components = ImmutableList.of(useJarCache, attachSourcesOnDemand, attachSourcesByDefault);
   }
 
   @Override
diff --git a/java/src/com/google/idea/blaze/java/sync/BlazeJavaLibrarySource.java b/java/src/com/google/idea/blaze/java/sync/BlazeJavaLibrarySource.java
new file mode 100644
index 0000000..0fd68e1
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/BlazeJavaLibrarySource.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.java.sync;
+
+import com.google.common.collect.ImmutableList;
+import com.google.idea.blaze.base.model.BlazeLibrary;
+import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.sync.libraries.LibrarySource;
+import com.google.idea.blaze.java.sync.model.BlazeJavaSyncData;
+import java.util.Collection;
+import java.util.function.Predicate;
+import javax.annotation.Nullable;
+
+class BlazeJavaLibrarySource extends LibrarySource.Adapter {
+
+  private final BlazeProjectData blazeProjectData;
+
+  BlazeJavaLibrarySource(BlazeProjectData blazeProjectData) {
+    this.blazeProjectData = blazeProjectData;
+  }
+
+  @Override
+  public Collection<? extends BlazeLibrary> getLibraries() {
+    BlazeJavaSyncData syncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class);
+    if (syncData == null) {
+      return ImmutableList.of();
+    }
+    return syncData.importResult.libraries.values();
+  }
+
+  @Nullable
+  @Override
+  public Predicate<BlazeLibrary> getLibraryFilter() {
+    BlazeJavaSyncData syncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class);
+    if (syncData == null) {
+      return o -> true;
+    }
+    return new LibraryGlobFilter(syncData.excludedLibraries);
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncAugmenter.java b/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncAugmenter.java
index 7bd32d9..5b5b23c 100644
--- a/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncAugmenter.java
+++ b/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncAugmenter.java
@@ -15,77 +15,26 @@
  */
 package com.google.idea.blaze.java.sync;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.projectview.section.Glob;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
 import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
-import com.google.idea.blaze.java.sync.model.BlazeLibrary;
 import com.intellij.openapi.extensions.ExtensionPointName;
 import java.util.Collection;
-import java.util.List;
 
 /** Augments the java importer */
 public interface BlazeJavaSyncAugmenter {
   ExtensionPointName<BlazeJavaSyncAugmenter> EP_NAME =
       ExtensionPointName.create("com.google.idea.blaze.java.JavaSyncAugmenter");
 
-  static Collection<BlazeJavaSyncAugmenter> getActiveSyncAgumenters(
-      WorkspaceLanguageSettings workspaceLanguageSettings) {
-    List<BlazeJavaSyncAugmenter> result = Lists.newArrayList();
-    for (BlazeJavaSyncAugmenter augmenter : EP_NAME.getExtensions()) {
-      if (augmenter.isActive(workspaceLanguageSettings)) {
-        result.add(augmenter);
-      }
-    }
-    return result;
-  }
-
-  boolean isActive(WorkspaceLanguageSettings workspaceLanguageSettings);
-
   /**
    * Adds extra libraries for this source rule.
    *
    * @param jars The output jars for the rule. Subject to jdeps optimization.
    * @param genJars Generated jars from this rule. Added unconditionally.
    */
-  void addJarsForSourceRule(
-      RuleIdeInfo rule, Collection<BlazeJarLibrary> jars, Collection<BlazeJarLibrary> genJars);
-
-  /**
-   * Adds any library filters. Useful if some libraries are supplied by this plugin in some other
-   * way, eg. via an SDK.
-   */
-  void addLibraryFilter(Glob.GlobSet excludedLibraries);
-
-  /** Called during the project structure phase to get additional libraries. */
-  Collection<BlazeLibrary> getAdditionalLibraries(BlazeProjectData blazeProjectData);
-
-  /**
-   * Returns a collection of library names for libraries that are added by some framework and
-   * shouldn't be removed during sync. Examples are typescript and dart support.
-   */
-  Collection<String> getExternallyAddedLibraries(BlazeProjectData blazeProjectData);
-
-  /** Adapter class for the sync augmenter interface */
-  abstract class Adapter implements BlazeJavaSyncAugmenter {
-    @Override
-    public void addJarsForSourceRule(
-        RuleIdeInfo rule, Collection<BlazeJarLibrary> jars, Collection<BlazeJarLibrary> genJars) {}
-
-    @Override
-    public void addLibraryFilter(Glob.GlobSet excludedLibraries) {}
-
-    @Override
-    public Collection<BlazeLibrary> getAdditionalLibraries(BlazeProjectData blazeProjectData) {
-      return ImmutableList.of();
-    }
-
-    @Override
-    public Collection<String> getExternallyAddedLibraries(BlazeProjectData blazeProjectData) {
-      return ImmutableList.of();
-    }
-  }
+  void addJarsForSourceTarget(
+      WorkspaceLanguageSettings workspaceLanguageSettings,
+      TargetIdeInfo target,
+      Collection<BlazeJarLibrary> jars,
+      Collection<BlazeJarLibrary> genJars);
 }
diff --git a/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncPlugin.java b/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncPlugin.java
index 088be73..dfca78e 100644
--- a/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncPlugin.java
+++ b/java/src/com/google/idea/blaze/java/sync/BlazeJavaSyncPlugin.java
@@ -19,7 +19,8 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
 import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
+import com.google.idea.blaze.base.model.BlazeLibrary;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.SyncState;
 import com.google.idea.blaze.base.model.primitives.LanguageClass;
@@ -35,6 +36,7 @@
 import com.google.idea.blaze.base.scope.scopes.TimingScope;
 import com.google.idea.blaze.base.settings.Blaze;
 import com.google.idea.blaze.base.sync.BlazeSyncPlugin;
+import com.google.idea.blaze.base.sync.libraries.LibrarySource;
 import com.google.idea.blaze.base.sync.projectview.WorkspaceLanguageSettings;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
 import com.google.idea.blaze.base.sync.workspace.BlazeRoots;
@@ -50,25 +52,20 @@
 import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
 import com.google.idea.blaze.java.sync.model.BlazeJavaImportResult;
 import com.google.idea.blaze.java.sync.model.BlazeJavaSyncData;
-import com.google.idea.blaze.java.sync.model.BlazeLibrary;
 import com.google.idea.blaze.java.sync.projectstructure.Jdks;
-import com.google.idea.blaze.java.sync.projectstructure.LibraryEditor;
 import com.google.idea.blaze.java.sync.projectstructure.SourceFolderEditor;
 import com.google.idea.blaze.java.sync.workingset.JavaWorkingSet;
 import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.module.Module;
 import com.intellij.openapi.module.ModuleType;
 import com.intellij.openapi.module.StdModuleTypes;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.projectRoots.Sdk;
 import com.intellij.openapi.roots.ContentEntry;
 import com.intellij.openapi.roots.LanguageLevelProjectExtension;
-import com.intellij.openapi.roots.ModifiableRootModel;
 import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
 import com.intellij.pom.java.LanguageLevel;
 import com.intellij.util.ui.UIUtil;
 import java.util.Collection;
-import java.util.List;
 import java.util.Set;
 import javax.annotation.Nullable;
 
@@ -76,6 +73,11 @@
 public class BlazeJavaSyncPlugin extends BlazeSyncPlugin.Adapter {
   private final JdepsFileReader jdepsFileReader = new JdepsFileReader();
 
+  @Override
+  public ImmutableList<WorkspaceType> getSupportedWorkspaceTypes() {
+    return ImmutableList.of(WorkspaceType.JAVA);
+  }
+
   @Nullable
   @Override
   public WorkspaceType getDefaultWorkspaceType() {
@@ -110,7 +112,7 @@
       @Nullable WorkingSet workingSet,
       WorkspacePathResolver workspacePathResolver,
       ArtifactLocationDecoder artifactLocationDecoder,
-      RuleMap ruleMap,
+      TargetMap targetMap,
       SyncState.Builder syncStateBuilder,
       @Nullable SyncState previousSyncState) {
     JavaWorkingSet javaWorkingSet = null;
@@ -121,14 +123,14 @@
     }
 
     JavaSourceFilter sourceFilter =
-        new JavaSourceFilter(project, workspaceRoot, projectViewSet, ruleMap);
+        new JavaSourceFilter(project, workspaceRoot, projectViewSet, targetMap);
 
     JdepsMap jdepsMap =
         jdepsFileReader.loadJdepsFiles(
             project,
             context,
             artifactLocationDecoder,
-            sourceFilter.getSourceRules(),
+            sourceFilter.getSourceTargets(),
             syncStateBuilder,
             previousSyncState);
     if (context.isCancelled()) {
@@ -141,7 +143,7 @@
             workspaceRoot,
             projectViewSet,
             workspaceLanguageSettings,
-            ruleMap,
+            targetMap,
             sourceFilter,
             jdepsMap,
             javaWorkingSet,
@@ -159,10 +161,6 @@
                 .addAll(projectViewSet.listItems(ExcludeLibrarySection.KEY))
                 .addAll(projectViewSet.listItems(ExcludedLibrarySection.KEY))
                 .build());
-    for (BlazeJavaSyncAugmenter augmenter :
-        BlazeJavaSyncAugmenter.getActiveSyncAgumenters(workspaceLanguageSettings)) {
-      augmenter.addLibraryFilter(excludedLibraries);
-    }
     BlazeJavaSyncData syncData = new BlazeJavaSyncData(importResult, excludedLibraries);
     syncStateBuilder.put(BlazeJavaSyncData.class, syncData);
   }
@@ -198,34 +196,6 @@
     SourceFolderEditor.modifyContentEntries(syncData.importResult, contentEntries);
   }
 
-  @Override
-  public void updateProjectStructure(
-      Project project,
-      BlazeContext context,
-      WorkspaceRoot workspaceRoot,
-      ProjectViewSet projectViewSet,
-      BlazeProjectData blazeProjectData,
-      @Nullable BlazeProjectData oldBlazeProjectData,
-      ModuleEditor moduleEditor,
-      Module workspaceModule,
-      ModifiableRootModel workspaceModifiableModel) {
-    BlazeJavaSyncData syncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class);
-    if (syncData == null) {
-      return;
-    }
-
-    List<BlazeLibrary> libraries = BlazeLibraryCollector.getLibraries(blazeProjectData);
-
-    Scope.push(
-        context,
-        childContext -> {
-          childContext.push(new TimingScope("UpdateLibraries"));
-          LibraryEditor.updateProjectLibraries(project, context, blazeProjectData, libraries);
-        });
-
-    LibraryEditor.configureDependencies(workspaceModifiableModel, libraries);
-  }
-
   private static void updateJdk(
       Project project,
       BlazeContext context,
@@ -286,6 +256,15 @@
         JavaLanguageLevelSection.PARSER);
   }
 
+  @Nullable
+  @Override
+  public LibrarySource getLibrarySource(BlazeProjectData blazeProjectData) {
+    if (!blazeProjectData.workspaceLanguageSettings.isLanguageActive(LanguageClass.JAVA)) {
+      return null;
+    }
+    return new BlazeJavaLibrarySource(blazeProjectData);
+  }
+
   /**
    * Looks at your jars for anything that seems to be a deploy jar and warns about it. This often
    * turns out to be a duplicate copy of all your application's code, so you don't want it in your
diff --git a/java/src/com/google/idea/blaze/java/sync/BlazeLibraryCollector.java b/java/src/com/google/idea/blaze/java/sync/BlazeLibraryCollector.java
deleted file mode 100644
index 99cd0b5..0000000
--- a/java/src/com/google/idea/blaze/java/sync/BlazeLibraryCollector.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2016 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.idea.blaze.java.sync;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.model.BlazeProjectData;
-import com.google.idea.blaze.base.projectview.section.Glob;
-import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
-import com.google.idea.blaze.java.sync.model.BlazeJavaSyncData;
-import com.google.idea.blaze.java.sync.model.BlazeLibrary;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/** Collects libraries from the sync data using all contributors. */
-public class BlazeLibraryCollector {
-  public static List<BlazeLibrary> getLibraries(BlazeProjectData blazeProjectData) {
-    BlazeJavaSyncData syncData = blazeProjectData.syncState.get(BlazeJavaSyncData.class);
-    if (syncData == null) {
-      return ImmutableList.of();
-    }
-
-    Glob.GlobSet excludedLibraries = syncData.excludedLibraries;
-
-    List<BlazeLibrary> libraries = Lists.newArrayList();
-    libraries.addAll(syncData.importResult.libraries.values());
-    for (BlazeJavaSyncAugmenter augmenter :
-        BlazeJavaSyncAugmenter.getActiveSyncAgumenters(
-            blazeProjectData.workspaceLanguageSettings)) {
-      libraries.addAll(augmenter.getAdditionalLibraries(blazeProjectData));
-    }
-    return libraries
-        .stream()
-        .filter(blazeLibrary -> !isExcluded(excludedLibraries, blazeLibrary))
-        .collect(Collectors.toList());
-  }
-
-  private static boolean isExcluded(Glob.GlobSet excludedLibraries, BlazeLibrary blazeLibrary) {
-    if (!(blazeLibrary instanceof BlazeJarLibrary)) {
-      return false;
-    }
-    BlazeJarLibrary jarLibrary = (BlazeJarLibrary) blazeLibrary;
-    ArtifactLocation interfaceJar = jarLibrary.libraryArtifact.interfaceJar;
-    ArtifactLocation classJar = jarLibrary.libraryArtifact.classJar;
-    return (interfaceJar != null && excludedLibraries.matches(interfaceJar.getRelativePath()))
-        || (classJar != null && excludedLibraries.matches(classJar.getRelativePath()));
-  }
-}
diff --git a/java/src/com/google/idea/blaze/java/sync/DuplicateSourceDetector.java b/java/src/com/google/idea/blaze/java/sync/DuplicateSourceDetector.java
index 13d5fbf..10db930 100644
--- a/java/src/com/google/idea/blaze/java/sync/DuplicateSourceDetector.java
+++ b/java/src/com/google/idea/blaze/java/sync/DuplicateSourceDetector.java
@@ -20,7 +20,7 @@
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Sets;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.scope.BlazeContext;
 import com.google.idea.blaze.base.scope.output.PerformanceWarning;
 import java.util.Collection;
@@ -30,30 +30,30 @@
 
 /** Detects and reports duplicate sources */
 public class DuplicateSourceDetector {
-  Multimap<ArtifactLocation, RuleKey> artifacts = ArrayListMultimap.create();
+  Multimap<ArtifactLocation, TargetKey> artifacts = ArrayListMultimap.create();
 
-  public void add(RuleKey ruleKey, ArtifactLocation artifactLocation) {
-    artifacts.put(artifactLocation, ruleKey);
+  public void add(TargetKey targetKey, ArtifactLocation artifactLocation) {
+    artifacts.put(artifactLocation, targetKey);
   }
 
   static class Duplicate {
     final ArtifactLocation artifactLocation;
-    final Collection<RuleKey> rules;
+    final Collection<TargetKey> targets;
 
-    public Duplicate(ArtifactLocation artifactLocation, Collection<RuleKey> rules) {
+    public Duplicate(ArtifactLocation artifactLocation, Collection<TargetKey> targets) {
       this.artifactLocation = artifactLocation;
-      this.rules = rules;
+      this.targets = targets;
     }
   }
 
   public void reportDuplicates(BlazeContext context) {
     List<Duplicate> duplicates = Lists.newArrayList();
     for (ArtifactLocation key : artifacts.keySet()) {
-      Collection<RuleKey> labels = artifacts.get(key);
+      Collection<TargetKey> labels = artifacts.get(key);
       if (labels.size() > 1) {
 
         // Workaround for aspect bug. Can be removed after the next blaze release, as of May 27 2016
-        Set<RuleKey> labelSet = Sets.newHashSet(labels);
+        Set<TargetKey> labelSet = Sets.newHashSet(labels);
         if (labelSet.size() > 1) {
           duplicates.add(new Duplicate(key, labelSet));
         }
@@ -76,8 +76,8 @@
       ArtifactLocation artifactLocation = duplicate.artifactLocation;
       context.output(new PerformanceWarning("  Source: " + artifactLocation.getRelativePath()));
       context.output(new PerformanceWarning("  Consumed by rules:"));
-      for (RuleKey ruleKey : duplicate.rules) {
-        context.output(new PerformanceWarning("    " + ruleKey.label));
+      for (TargetKey targetKey : duplicate.targets) {
+        context.output(new PerformanceWarning("    " + targetKey.label));
       }
       context.output(new PerformanceWarning("")); // Newline
     }
diff --git a/java/src/com/google/idea/blaze/java/sync/JavaPrefetchFileSource.java b/java/src/com/google/idea/blaze/java/sync/JavaPrefetchFileSource.java
index 21b3cd2..7e3255f 100644
--- a/java/src/com/google/idea/blaze/java/sync/JavaPrefetchFileSource.java
+++ b/java/src/com/google/idea/blaze/java/sync/JavaPrefetchFileSource.java
@@ -16,15 +16,16 @@
 package com.google.idea.blaze.java.sync;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.idea.blaze.base.model.BlazeLibrary;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.prefetch.PrefetchFileSource;
+import com.google.idea.blaze.base.sync.libraries.BlazeLibraryCollector;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
 import com.google.idea.blaze.java.libraries.JarCache;
 import com.google.idea.blaze.java.libraries.SourceJarManager;
 import com.google.idea.blaze.java.settings.BlazeJavaUserSettings;
 import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
 import com.google.idea.blaze.java.sync.model.BlazeJavaSyncData;
-import com.google.idea.blaze.java.sync.model.BlazeLibrary;
 import com.intellij.openapi.project.Project;
 import java.io.File;
 import java.util.Collection;
diff --git a/java/src/com/google/idea/blaze/java/sync/LibraryGlobFilter.java b/java/src/com/google/idea/blaze/java/sync/LibraryGlobFilter.java
new file mode 100644
index 0000000..ea0f3cd
--- /dev/null
+++ b/java/src/com/google/idea/blaze/java/sync/LibraryGlobFilter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.idea.blaze.java.sync;
+
+import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
+import com.google.idea.blaze.base.model.BlazeLibrary;
+import com.google.idea.blaze.base.projectview.section.Glob.GlobSet;
+import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
+import java.util.function.Predicate;
+
+/** Filters out any libraries in a globset. */
+public class LibraryGlobFilter implements Predicate<BlazeLibrary> {
+
+  private final GlobSet excludedLibraries;
+
+  public LibraryGlobFilter(GlobSet excludedLibraries) {
+    this.excludedLibraries = excludedLibraries;
+  }
+
+  @Override
+  public boolean test(BlazeLibrary blazeLibrary) {
+    if (!(blazeLibrary instanceof BlazeJarLibrary)) {
+      return true;
+    }
+    BlazeJarLibrary jarLibrary = (BlazeJarLibrary) blazeLibrary;
+    ArtifactLocation interfaceJar = jarLibrary.libraryArtifact.interfaceJar;
+    ArtifactLocation classJar = jarLibrary.libraryArtifact.classJar;
+    boolean matches =
+        (interfaceJar != null && excludedLibraries.matches(interfaceJar.getRelativePath()))
+            || (classJar != null && excludedLibraries.matches(classJar.getRelativePath()));
+    return !matches;
+  }
+}
diff --git a/java/src/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporter.java b/java/src/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporter.java
index 4ef5bf8..80d7d83 100644
--- a/java/src/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporter.java
+++ b/java/src/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporter.java
@@ -25,13 +25,15 @@
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Sets;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.JavaRuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.Dependency;
+import com.google.idea.blaze.base.ideinfo.Dependency.DependencyType;
+import com.google.idea.blaze.base.ideinfo.JavaIdeInfo;
 import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
 import com.google.idea.blaze.base.ideinfo.ProtoLibraryLegacyInfo;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
-import com.google.idea.blaze.base.model.primitives.Label;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
+import com.google.idea.blaze.base.model.LibraryKey;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
 import com.google.idea.blaze.base.scope.BlazeContext;
@@ -47,11 +49,11 @@
 import com.google.idea.blaze.java.sync.model.BlazeContentEntry;
 import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
 import com.google.idea.blaze.java.sync.model.BlazeJavaImportResult;
-import com.google.idea.blaze.java.sync.model.LibraryKey;
 import com.google.idea.blaze.java.sync.source.SourceArtifact;
 import com.google.idea.blaze.java.sync.source.SourceDirectoryCalculator;
 import com.google.idea.blaze.java.sync.workingset.JavaWorkingSet;
 import com.intellij.openapi.project.Project;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -64,21 +66,22 @@
   private final Project project;
   private final WorkspaceRoot workspaceRoot;
   private final ImportRoots importRoots;
-  private final RuleMap ruleMap;
+  private final TargetMap targetMap;
   private final SourceTestConfig sourceTestConfig;
   private final JdepsMap jdepsMap;
   @Nullable private final JavaWorkingSet workingSet;
   private final ArtifactLocationDecoder artifactLocationDecoder;
   private final DuplicateSourceDetector duplicateSourceDetector = new DuplicateSourceDetector();
-  private final Collection<BlazeJavaSyncAugmenter> augmenters;
   private final JavaSourceFilter sourceFilter;
+  private final WorkspaceLanguageSettings workspaceLanguageSettings;
+  private final List<BlazeJavaSyncAugmenter> augmenters;
 
   public BlazeJavaWorkspaceImporter(
       Project project,
       WorkspaceRoot workspaceRoot,
       ProjectViewSet projectViewSet,
       WorkspaceLanguageSettings workspaceLanguageSettings,
-      RuleMap ruleMap,
+      TargetMap targetMap,
       JavaSourceFilter sourceFilter,
       JdepsMap jdepsMap,
       @Nullable JavaWorkingSet workingSet,
@@ -89,19 +92,20 @@
         ImportRoots.builder(workspaceRoot, Blaze.getBuildSystem(project))
             .add(projectViewSet)
             .build();
-    this.ruleMap = ruleMap;
+    this.targetMap = targetMap;
     this.sourceFilter = sourceFilter;
     this.jdepsMap = jdepsMap;
     this.workingSet = workingSet;
     this.artifactLocationDecoder = artifactLocationDecoder;
     this.sourceTestConfig = new SourceTestConfig(projectViewSet);
-    this.augmenters = BlazeJavaSyncAugmenter.getActiveSyncAgumenters(workspaceLanguageSettings);
+    this.workspaceLanguageSettings = workspaceLanguageSettings;
+    this.augmenters = Arrays.asList(BlazeJavaSyncAugmenter.EP_NAME.getExtensions());
   }
 
   public BlazeJavaImportResult importWorkspace(BlazeContext context) {
     WorkspaceBuilder workspaceBuilder = new WorkspaceBuilder();
-    for (RuleIdeInfo rule : sourceFilter.sourceRules) {
-      addRuleAsSource(workspaceBuilder, rule, sourceFilter.ruleToJavaSources.get(rule.key));
+    for (TargetIdeInfo target : sourceFilter.sourceTargets) {
+      addTargetAsSource(workspaceBuilder, target, sourceFilter.targetToJavaSources.get(target.key));
     }
 
     SourceDirectoryCalculator sourceDirectoryCalculator = new SourceDirectoryCalculator();
@@ -124,11 +128,11 @@
 
     ImmutableMap<LibraryKey, BlazeJarLibrary> libraries =
         buildLibraries(
-            workspaceBuilder, ruleMap, sourceFilter.libraryRules, sourceFilter.protoLibraries);
+            workspaceBuilder, targetMap, sourceFilter.libraryTargets, sourceFilter.protoLibraries);
 
     duplicateSourceDetector.reportDuplicates(context);
 
-    String sourceVersion = findSourceVersion(ruleMap);
+    String sourceVersion = findSourceVersion(targetMap);
 
     return new BlazeJavaImportResult(
         contentEntries,
@@ -141,44 +145,44 @@
 
   private ImmutableMap<LibraryKey, BlazeJarLibrary> buildLibraries(
       WorkspaceBuilder workspaceBuilder,
-      RuleMap ruleMap,
-      List<RuleIdeInfo> libraryRules,
-      List<RuleIdeInfo> protoLibraries) {
+      TargetMap targetMap,
+      List<TargetIdeInfo> libraryTargets,
+      List<TargetIdeInfo> protoLibraries) {
     // Build library maps
-    Multimap<RuleKey, BlazeJarLibrary> ruleKeyToLibrary = ArrayListMultimap.create();
+    Multimap<TargetKey, BlazeJarLibrary> targetKeyToLibrary = ArrayListMultimap.create();
     Map<String, BlazeJarLibrary> jdepsPathToLibrary = Maps.newHashMap();
 
     // Add any output jars from source rules
-    for (RuleKey key : workspaceBuilder.outputJarsFromSourceRules.keySet()) {
-      Collection<BlazeJarLibrary> jars = workspaceBuilder.outputJarsFromSourceRules.get(key);
-      ruleKeyToLibrary.putAll(key, jars);
+    for (TargetKey key : workspaceBuilder.outputJarsFromSourceTargets.keySet()) {
+      Collection<BlazeJarLibrary> jars = workspaceBuilder.outputJarsFromSourceTargets.get(key);
+      targetKeyToLibrary.putAll(key, jars);
       for (BlazeJarLibrary library : jars) {
         addLibraryToJdeps(jdepsPathToLibrary, library);
       }
     }
 
-    for (RuleIdeInfo rule : libraryRules) {
-      JavaRuleIdeInfo javaRuleIdeInfo = rule.javaRuleIdeInfo;
-      if (javaRuleIdeInfo == null) {
+    for (TargetIdeInfo target : libraryTargets) {
+      JavaIdeInfo javaIdeInfo = target.javaIdeInfo;
+      if (javaIdeInfo == null) {
         continue;
       }
       List<LibraryArtifact> allJars = Lists.newArrayList();
-      allJars.addAll(javaRuleIdeInfo.jars);
+      allJars.addAll(javaIdeInfo.jars);
       Collection<BlazeJarLibrary> libraries =
           allJars
               .stream()
-              .map(library -> new BlazeJarLibrary(library, rule.key))
+              .map(library -> new BlazeJarLibrary(library, target.key))
               .collect(Collectors.toList());
 
-      ruleKeyToLibrary.putAll(rule.key, libraries);
+      targetKeyToLibrary.putAll(target.key, libraries);
       for (BlazeJarLibrary library : libraries) {
         addLibraryToJdeps(jdepsPathToLibrary, library);
       }
     }
 
     // proto legacy jdeps support
-    for (RuleIdeInfo rule : protoLibraries) {
-      ProtoLibraryLegacyInfo protoLibraryLegacyInfo = rule.protoLibraryLegacyInfo;
+    for (TargetIdeInfo target : protoLibraries) {
+      ProtoLibraryLegacyInfo protoLibraryLegacyInfo = target.protoLibraryLegacyInfo;
       if (protoLibraryLegacyInfo == null) {
         continue;
       }
@@ -187,7 +191,7 @@
               protoLibraryLegacyInfo.jarsV1,
               protoLibraryLegacyInfo.jarsMutable,
               protoLibraryLegacyInfo.jarsImmutable)) {
-        addLibraryToJdeps(jdepsPathToLibrary, new BlazeJarLibrary(libraryArtifact, rule.key));
+        addLibraryToJdeps(jdepsPathToLibrary, new BlazeJarLibrary(libraryArtifact, target.key));
       }
     }
 
@@ -202,17 +206,17 @@
     }
 
     // Collect jars referenced by direct deps from your working set
-    for (RuleKey deps : workspaceBuilder.directDeps) {
-      for (BlazeJarLibrary library : ruleKeyToLibrary.get(deps)) {
+    for (TargetKey deps : workspaceBuilder.directDeps) {
+      for (BlazeJarLibrary library : targetKeyToLibrary.get(deps)) {
         result.put(library.key, library);
       }
     }
 
     // Collect legacy proto libraries from direct deps
-    addProtoLegacyLibrariesFromDirectDeps(workspaceBuilder, ruleMap, result);
+    addProtoLegacyLibrariesFromDirectDeps(workspaceBuilder, targetMap, result);
 
     // Collect generated jars from source rules
-    for (BlazeJarLibrary library : workspaceBuilder.generatedJarsFromSourceRules) {
+    for (BlazeJarLibrary library : workspaceBuilder.generatedJarsFromSourceTargets) {
       result.put(library.key, library);
     }
 
@@ -220,32 +224,34 @@
   }
 
   private void addProtoLegacyLibrariesFromDirectDeps(
-      WorkspaceBuilder workspaceBuilder, RuleMap ruleMap, Map<LibraryKey, BlazeJarLibrary> result) {
-    List<RuleKey> version1Roots = Lists.newArrayList();
-    List<RuleKey> immutableRoots = Lists.newArrayList();
-    List<RuleKey> mutableRoots = Lists.newArrayList();
-    for (RuleKey ruleKey : workspaceBuilder.directDeps) {
-      RuleIdeInfo rule = ruleMap.get(ruleKey);
-      if (rule == null) {
+      WorkspaceBuilder workspaceBuilder,
+      TargetMap targetMap,
+      Map<LibraryKey, BlazeJarLibrary> result) {
+    List<TargetKey> version1Roots = Lists.newArrayList();
+    List<TargetKey> immutableRoots = Lists.newArrayList();
+    List<TargetKey> mutableRoots = Lists.newArrayList();
+    for (TargetKey targetKey : workspaceBuilder.directDeps) {
+      TargetIdeInfo target = targetMap.get(targetKey);
+      if (target == null) {
         continue;
       }
-      ProtoLibraryLegacyInfo protoLibraryLegacyInfo = rule.protoLibraryLegacyInfo;
+      ProtoLibraryLegacyInfo protoLibraryLegacyInfo = target.protoLibraryLegacyInfo;
       if (protoLibraryLegacyInfo == null) {
         continue;
       }
       switch (protoLibraryLegacyInfo.apiFlavor) {
         case VERSION_1:
-          version1Roots.add(ruleKey);
+          version1Roots.add(targetKey);
           break;
         case IMMUTABLE:
-          immutableRoots.add(ruleKey);
+          immutableRoots.add(targetKey);
           break;
         case MUTABLE:
-          mutableRoots.add(ruleKey);
+          mutableRoots.add(targetKey);
           break;
         case BOTH:
-          mutableRoots.add(ruleKey);
-          immutableRoots.add(ruleKey);
+          mutableRoots.add(targetKey);
+          immutableRoots.add(targetKey);
           break;
         default:
           // Can't happen
@@ -254,29 +260,29 @@
     }
 
     addProtoLegacyLibrariesFromDirectDepsForFlavor(
-        ruleMap, ProtoLibraryLegacyInfo.ApiFlavor.VERSION_1, version1Roots, result);
+        targetMap, ProtoLibraryLegacyInfo.ApiFlavor.VERSION_1, version1Roots, result);
     addProtoLegacyLibrariesFromDirectDepsForFlavor(
-        ruleMap, ProtoLibraryLegacyInfo.ApiFlavor.IMMUTABLE, immutableRoots, result);
+        targetMap, ProtoLibraryLegacyInfo.ApiFlavor.IMMUTABLE, immutableRoots, result);
     addProtoLegacyLibrariesFromDirectDepsForFlavor(
-        ruleMap, ProtoLibraryLegacyInfo.ApiFlavor.MUTABLE, mutableRoots, result);
+        targetMap, ProtoLibraryLegacyInfo.ApiFlavor.MUTABLE, mutableRoots, result);
   }
 
   private void addProtoLegacyLibrariesFromDirectDepsForFlavor(
-      RuleMap ruleMap,
+      TargetMap targetMap,
       ProtoLibraryLegacyInfo.ApiFlavor apiFlavor,
-      List<RuleKey> roots,
+      List<TargetKey> roots,
       Map<LibraryKey, BlazeJarLibrary> result) {
-    Set<RuleKey> seen = Sets.newHashSet();
+    Set<TargetKey> seen = Sets.newHashSet();
     while (!roots.isEmpty()) {
-      RuleKey ruleKey = roots.remove(roots.size() - 1);
-      if (!seen.add(ruleKey)) {
+      TargetKey targetKey = roots.remove(roots.size() - 1);
+      if (!seen.add(targetKey)) {
         continue;
       }
-      RuleIdeInfo rule = ruleMap.get(ruleKey);
-      if (rule == null) {
+      TargetIdeInfo target = targetMap.get(targetKey);
+      if (target == null) {
         continue;
       }
-      ProtoLibraryLegacyInfo protoLibraryLegacyInfo = rule.protoLibraryLegacyInfo;
+      ProtoLibraryLegacyInfo protoLibraryLegacyInfo = target.protoLibraryLegacyInfo;
       if (protoLibraryLegacyInfo == null) {
         continue;
       }
@@ -299,13 +305,15 @@
 
       if (libraries != null) {
         for (LibraryArtifact libraryArtifact : libraries) {
-          BlazeJarLibrary library = new BlazeJarLibrary(libraryArtifact, ruleKey);
+          BlazeJarLibrary library = new BlazeJarLibrary(libraryArtifact, targetKey);
           result.put(library.key, library);
         }
       }
 
-      for (Label dep : rule.dependencies) {
-        roots.add(RuleKey.forDependency(rule, dep));
+      for (Dependency dep : target.dependencies) {
+        if (dep.dependencyType == DependencyType.COMPILE_TIME) {
+          roots.add(dep.targetKey);
+        }
       }
     }
   }
@@ -323,72 +331,75 @@
     }
   }
 
-  private void addRuleAsSource(
+  private void addTargetAsSource(
       WorkspaceBuilder workspaceBuilder,
-      RuleIdeInfo rule,
+      TargetIdeInfo target,
       Collection<ArtifactLocation> javaSources) {
-    JavaRuleIdeInfo javaRuleIdeInfo = rule.javaRuleIdeInfo;
-    if (javaRuleIdeInfo == null) {
+    JavaIdeInfo javaIdeInfo = target.javaIdeInfo;
+    if (javaIdeInfo == null) {
       return;
     }
 
-    RuleKey ruleKey = rule.key;
-    Collection<String> jars = jdepsMap.getDependenciesForRule(ruleKey);
+    TargetKey targetKey = target.key;
+    Collection<String> jars = jdepsMap.getDependenciesForTarget(targetKey);
     if (jars != null) {
       workspaceBuilder.jdeps.addAll(jars);
     }
 
     // Add all deps if this rule is in the current working set
-    if (workingSet == null || workingSet.isRuleInWorkingSet(rule)) {
+    if (workingSet == null || workingSet.isTargetInWorkingSet(target)) {
       // Add self, so we pick up our own gen jars if in working set
-      workspaceBuilder.directDeps.add(ruleKey);
-      for (Label dep : rule.dependencies) {
-        workspaceBuilder.directDeps.add(RuleKey.forDependency(rule, dep));
+      workspaceBuilder.directDeps.add(targetKey);
+      for (Dependency dep : target.dependencies) {
+        if (dep.dependencyType == DependencyType.COMPILE_TIME) {
+          workspaceBuilder.directDeps.add(dep.targetKey);
+        }
       }
     }
 
     for (ArtifactLocation artifactLocation : javaSources) {
       if (artifactLocation.isSource()) {
-        duplicateSourceDetector.add(ruleKey, artifactLocation);
-        workspaceBuilder.sourceArtifacts.add(new SourceArtifact(ruleKey, artifactLocation));
+        duplicateSourceDetector.add(targetKey, artifactLocation);
+        workspaceBuilder.sourceArtifacts.add(new SourceArtifact(targetKey, artifactLocation));
         workspaceBuilder.addedSourceFiles.add(artifactLocation);
       }
     }
 
-    ArtifactLocation manifest = javaRuleIdeInfo.packageManifest;
+    ArtifactLocation manifest = javaIdeInfo.packageManifest;
     if (manifest != null) {
-      workspaceBuilder.javaPackageManifests.put(ruleKey, manifest);
+      workspaceBuilder.javaPackageManifests.put(targetKey, manifest);
     }
-    for (LibraryArtifact libraryArtifact : javaRuleIdeInfo.jars) {
+    for (LibraryArtifact libraryArtifact : javaIdeInfo.jars) {
       ArtifactLocation classJar = libraryArtifact.classJar;
       if (classJar != null) {
         workspaceBuilder.buildOutputJars.add(classJar);
       }
     }
-    workspaceBuilder.generatedJarsFromSourceRules.addAll(
-        javaRuleIdeInfo
+    workspaceBuilder.generatedJarsFromSourceTargets.addAll(
+        javaIdeInfo
             .generatedJars
             .stream()
-            .map(libraryArtifact -> new BlazeJarLibrary(libraryArtifact, ruleKey))
+            .map(libraryArtifact -> new BlazeJarLibrary(libraryArtifact, targetKey))
             .collect(Collectors.toList()));
-    if (javaRuleIdeInfo.filteredGenJar != null) {
-      workspaceBuilder.generatedJarsFromSourceRules.add(
-          new BlazeJarLibrary(javaRuleIdeInfo.filteredGenJar, ruleKey));
+    if (javaIdeInfo.filteredGenJar != null) {
+      workspaceBuilder.generatedJarsFromSourceTargets.add(
+          new BlazeJarLibrary(javaIdeInfo.filteredGenJar, targetKey));
     }
 
     for (BlazeJavaSyncAugmenter augmenter : augmenters) {
-      augmenter.addJarsForSourceRule(
-          rule,
-          workspaceBuilder.outputJarsFromSourceRules.get(ruleKey),
-          workspaceBuilder.generatedJarsFromSourceRules);
+      augmenter.addJarsForSourceTarget(
+          workspaceLanguageSettings,
+          target,
+          workspaceBuilder.outputJarsFromSourceTargets.get(targetKey),
+          workspaceBuilder.generatedJarsFromSourceTargets);
     }
   }
 
   @Nullable
-  private String findSourceVersion(RuleMap ruleMap) {
-    for (RuleIdeInfo rule : ruleMap.rules()) {
-      if (rule.javaToolchainIdeInfo != null) {
-        return rule.javaToolchainIdeInfo.sourceVersion;
+  private String findSourceVersion(TargetMap targetMap) {
+    for (TargetIdeInfo target : targetMap.targets()) {
+      if (target.javaToolchainIdeInfo != null) {
+        return target.javaToolchainIdeInfo.sourceVersion;
       }
     }
     return null;
@@ -396,12 +407,12 @@
 
   private static class WorkspaceBuilder {
     Set<String> jdeps = Sets.newHashSet();
-    Set<RuleKey> directDeps = Sets.newHashSet();
+    Set<TargetKey> directDeps = Sets.newHashSet();
     Set<ArtifactLocation> addedSourceFiles = Sets.newHashSet();
-    Multimap<RuleKey, BlazeJarLibrary> outputJarsFromSourceRules = ArrayListMultimap.create();
-    List<BlazeJarLibrary> generatedJarsFromSourceRules = Lists.newArrayList();
+    Multimap<TargetKey, BlazeJarLibrary> outputJarsFromSourceTargets = ArrayListMultimap.create();
+    List<BlazeJarLibrary> generatedJarsFromSourceTargets = Lists.newArrayList();
     List<ArtifactLocation> buildOutputJars = Lists.newArrayList();
     List<SourceArtifact> sourceArtifacts = Lists.newArrayList();
-    Map<RuleKey, ArtifactLocation> javaPackageManifests = Maps.newHashMap();
+    Map<TargetKey, ArtifactLocation> javaPackageManifests = Maps.newHashMap();
   }
 }
diff --git a/java/src/com/google/idea/blaze/java/sync/importer/JavaSourceFilter.java b/java/src/com/google/idea/blaze/java/sync/importer/JavaSourceFilter.java
index 0474836..579eede 100644
--- a/java/src/com/google/idea/blaze/java/sync/importer/JavaSourceFilter.java
+++ b/java/src/com/google/idea/blaze/java/sync/importer/JavaSourceFilter.java
@@ -18,14 +18,13 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
 import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
-import com.google.idea.blaze.base.sync.projectview.ProjectViewRuleImportFilter;
-import com.google.idea.common.experiments.BoolExperiment;
+import com.google.idea.blaze.base.sync.projectview.ProjectViewTargetImportFilter;
 import com.intellij.openapi.project.Project;
 import java.util.Collection;
 import java.util.List;
@@ -34,79 +33,70 @@
 
 /** Segments java rules into source/libraries */
 public class JavaSourceFilter {
-  private static final BoolExperiment NO_EMPTY_SOURCE_RULES =
-      new BoolExperiment("no.empty.source.rules", true);
-
-  final List<RuleIdeInfo> sourceRules;
-  final List<RuleIdeInfo> libraryRules;
-  final List<RuleIdeInfo> protoLibraries;
-  final Map<RuleKey, Collection<ArtifactLocation>> ruleToJavaSources;
+  final List<TargetIdeInfo> sourceTargets;
+  final List<TargetIdeInfo> libraryTargets;
+  final List<TargetIdeInfo> protoLibraries;
+  final Map<TargetKey, Collection<ArtifactLocation>> targetToJavaSources;
 
   public JavaSourceFilter(
       Project project,
       WorkspaceRoot workspaceRoot,
       ProjectViewSet projectViewSet,
-      RuleMap ruleMap) {
-    ProjectViewRuleImportFilter importFilter =
-        new ProjectViewRuleImportFilter(project, workspaceRoot, projectViewSet);
-    List<RuleIdeInfo> includedRules =
-        ruleMap
-            .rules()
+      TargetMap targetMap) {
+    ProjectViewTargetImportFilter importFilter =
+        new ProjectViewTargetImportFilter(project, workspaceRoot, projectViewSet);
+    List<TargetIdeInfo> includedTargets =
+        targetMap
+            .targets()
             .stream()
-            .filter(rule -> !importFilter.excludeTarget(rule))
+            .filter(target -> !importFilter.excludeTarget(target))
             .collect(Collectors.toList());
 
-    List<RuleIdeInfo> javaRules =
-        includedRules
+    List<TargetIdeInfo> javaTargets =
+        includedTargets
             .stream()
-            .filter(rule -> rule.javaRuleIdeInfo != null)
+            .filter(target -> target.javaIdeInfo != null)
             .collect(Collectors.toList());
 
-    ruleToJavaSources = Maps.newHashMap();
-    for (RuleIdeInfo rule : javaRules) {
+    targetToJavaSources = Maps.newHashMap();
+    for (TargetIdeInfo target : javaTargets) {
       List<ArtifactLocation> javaSources =
-          rule.sources
+          target
+              .sources
               .stream()
               .filter(source -> source.getRelativePath().endsWith(".java"))
               .collect(Collectors.toList());
-      ruleToJavaSources.put(rule.key, javaSources);
+      targetToJavaSources.put(target.key, javaSources);
     }
 
-    boolean noEmptySourceRules = NO_EMPTY_SOURCE_RULES.getValue();
-    sourceRules = Lists.newArrayList();
-    libraryRules = Lists.newArrayList();
-    for (RuleIdeInfo rule : javaRules) {
+    sourceTargets = Lists.newArrayList();
+    libraryTargets = Lists.newArrayList();
+    for (TargetIdeInfo target : javaTargets) {
       boolean importAsSource =
-          importFilter.isSourceRule(rule)
-              && canImportAsSource(rule)
-              && (noEmptySourceRules
-                  ? anyNonGeneratedSources(ruleToJavaSources.get(rule.key))
-                  : !allSourcesGenerated(ruleToJavaSources.get(rule.key)));
+          importFilter.isSourceTarget(target)
+              && canImportAsSource(target)
+              && (anyNonGeneratedSources(targetToJavaSources.get(target.key)));
 
       if (importAsSource) {
-        sourceRules.add(rule);
+        sourceTargets.add(target);
       } else {
-        libraryRules.add(rule);
+        libraryTargets.add(target);
       }
     }
 
     protoLibraries =
-        includedRules
+        includedTargets
             .stream()
-            .filter(rule -> rule.kind == Kind.PROTO_LIBRARY)
+            .filter(target -> target.kind == Kind.PROTO_LIBRARY)
             .collect(Collectors.toList());
   }
 
-  public Iterable<RuleIdeInfo> getSourceRules() {
-    return sourceRules;
+  public Iterable<TargetIdeInfo> getSourceTargets() {
+    return sourceTargets;
   }
 
-  private boolean canImportAsSource(RuleIdeInfo rule) {
-    return !rule.kindIsOneOf(Kind.JAVA_WRAP_CC, Kind.JAVA_IMPORT);
-  }
-
-  private boolean allSourcesGenerated(Collection<ArtifactLocation> sources) {
-    return !sources.isEmpty() && sources.stream().allMatch(ArtifactLocation::isGenerated);
+  private boolean canImportAsSource(TargetIdeInfo target) {
+    return !target.kindIsOneOf(Kind.JAVA_WRAP_CC, Kind.JAVA_IMPORT);
   }
 
   private boolean anyNonGeneratedSources(Collection<ArtifactLocation> sources) {
diff --git a/java/src/com/google/idea/blaze/java/sync/jdeps/JdepsFileReader.java b/java/src/com/google/idea/blaze/java/sync/jdeps/JdepsFileReader.java
index c4bdaac..ed9bf6a 100644
--- a/java/src/com/google/idea/blaze/java/sync/jdeps/JdepsFileReader.java
+++ b/java/src/com/google/idea/blaze/java/sync/jdeps/JdepsFileReader.java
@@ -24,9 +24,9 @@
 import com.google.idea.blaze.base.async.executor.BlazeExecutor;
 import com.google.idea.blaze.base.filecache.FileDiffer;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.JavaRuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.JavaIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.model.SyncState;
 import com.google.idea.blaze.base.prefetch.PrefetchService;
 import com.google.idea.blaze.base.scope.BlazeContext;
@@ -56,18 +56,18 @@
   static class JdepsState implements Serializable {
     private static final long serialVersionUID = 4L;
     private ImmutableMap<File, Long> fileState = null;
-    private Map<File, RuleKey> fileToRuleMap = Maps.newHashMap();
-    private Map<RuleKey, List<String>> ruleToJdeps = Maps.newHashMap();
+    private Map<File, TargetKey> fileToTargetMap = Maps.newHashMap();
+    private Map<TargetKey, List<String>> targetToJdeps = Maps.newHashMap();
   }
 
   private static class Result {
     File file;
-    RuleKey ruleKey;
+    TargetKey targetKey;
     List<String> dependencies;
 
-    public Result(File file, RuleKey ruleKey, List<String> dependencies) {
+    public Result(File file, TargetKey targetKey, List<String> dependencies) {
       this.file = file;
-      this.ruleKey = ruleKey;
+      this.targetKey = targetKey;
       this.dependencies = dependencies;
     }
   }
@@ -78,7 +78,7 @@
       Project project,
       BlazeContext parentContext,
       ArtifactLocationDecoder artifactLocationDecoder,
-      Iterable<RuleIdeInfo> rulesToLoad,
+      Iterable<TargetIdeInfo> targetsToLoad,
       SyncState.Builder syncStateBuilder,
       @Nullable SyncState previousSyncState) {
     JdepsState oldState =
@@ -89,13 +89,13 @@
             (context) -> {
               context.push(new TimingScope("LoadJdepsFiles"));
               return doLoadJdepsFiles(
-                  project, context, artifactLocationDecoder, oldState, rulesToLoad);
+                  project, context, artifactLocationDecoder, oldState, targetsToLoad);
             });
     if (jdepsState == null) {
       return null;
     }
     syncStateBuilder.put(JdepsState.class, jdepsState);
-    return ruleKey -> jdepsState.ruleToJdeps.get(ruleKey);
+    return targetKey -> jdepsState.targetToJdeps.get(targetKey);
   }
 
   private JdepsState doLoadJdepsFiles(
@@ -103,21 +103,21 @@
       BlazeContext context,
       ArtifactLocationDecoder artifactLocationDecoder,
       @Nullable JdepsState oldState,
-      Iterable<RuleIdeInfo> rulesToLoad) {
+      Iterable<TargetIdeInfo> targetsToLoad) {
     JdepsState state = new JdepsState();
     if (oldState != null) {
-      state.ruleToJdeps = Maps.newHashMap(oldState.ruleToJdeps);
-      state.fileToRuleMap = Maps.newHashMap(oldState.fileToRuleMap);
+      state.targetToJdeps = Maps.newHashMap(oldState.targetToJdeps);
+      state.fileToTargetMap = Maps.newHashMap(oldState.fileToTargetMap);
     }
 
-    Map<File, RuleKey> fileToRuleMap = Maps.newHashMap();
-    for (RuleIdeInfo ruleIdeInfo : rulesToLoad) {
-      assert ruleIdeInfo != null;
-      JavaRuleIdeInfo javaRuleIdeInfo = ruleIdeInfo.javaRuleIdeInfo;
-      if (javaRuleIdeInfo != null) {
-        ArtifactLocation jdepsFile = javaRuleIdeInfo.jdepsFile;
+    Map<File, TargetKey> fileToTargetMap = Maps.newHashMap();
+    for (TargetIdeInfo target : targetsToLoad) {
+      assert target != null;
+      JavaIdeInfo javaIdeInfo = target.javaIdeInfo;
+      if (javaIdeInfo != null) {
+        ArtifactLocation jdepsFile = javaIdeInfo.jdepsFile;
         if (jdepsFile != null) {
-          fileToRuleMap.put(artifactLocationDecoder.decode(jdepsFile), ruleIdeInfo.key);
+          fileToTargetMap.put(artifactLocationDecoder.decode(jdepsFile), target.key);
         }
       }
     }
@@ -127,7 +127,7 @@
     state.fileState =
         FileDiffer.updateFiles(
             oldState != null ? oldState.fileState : null,
-            fileToRuleMap.keySet(),
+            fileToTargetMap.keySet(),
             updatedFiles,
             removedFiles);
 
@@ -142,9 +142,9 @@
     }
 
     for (File removedFile : removedFiles) {
-      RuleKey ruleKey = state.fileToRuleMap.remove(removedFile);
-      if (ruleKey != null) {
-        state.ruleToJdeps.remove(ruleKey);
+      TargetKey targetKey = state.fileToTargetMap.remove(removedFile);
+      if (targetKey != null) {
+        state.targetToJdeps.remove(targetKey);
       }
     }
 
@@ -169,8 +169,8 @@
                         dependencyStringList.add(dependency.getPath());
                       }
                     }
-                    RuleKey ruleKey = fileToRuleMap.get(updatedFile);
-                    return new Result(updatedFile, ruleKey, dependencyStringList);
+                    TargetKey targetKey = fileToTargetMap.get(updatedFile);
+                    return new Result(updatedFile, targetKey, dependencyStringList);
                   }
                 } catch (FileNotFoundException e) {
                   LOG.info("Could not open jdeps file: " + updatedFile);
@@ -181,8 +181,8 @@
     try {
       for (Result result : Futures.allAsList(futures).get()) {
         if (result != null) {
-          state.fileToRuleMap.put(result.file, result.ruleKey);
-          state.ruleToJdeps.put(result.ruleKey, result.dependencies);
+          state.fileToTargetMap.put(result.file, result.targetKey);
+          state.targetToJdeps.put(result.targetKey, result.dependencies);
         }
       }
       context.output(
diff --git a/java/src/com/google/idea/blaze/java/sync/jdeps/JdepsMap.java b/java/src/com/google/idea/blaze/java/sync/jdeps/JdepsMap.java
index 32b5452..ac53134 100644
--- a/java/src/com/google/idea/blaze/java/sync/jdeps/JdepsMap.java
+++ b/java/src/com/google/idea/blaze/java/sync/jdeps/JdepsMap.java
@@ -15,7 +15,7 @@
  */
 package com.google.idea.blaze.java.sync.jdeps;
 
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import java.util.List;
 import org.jetbrains.annotations.Nullable;
 
@@ -30,5 +30,5 @@
    * <p>If the rule doesn't have source or otherwise wasn't instrumented, null is returned.
    */
   @Nullable
-  List<String> getDependenciesForRule(RuleKey ruleKey);
+  List<String> getDependenciesForTarget(TargetKey targetKey);
 }
diff --git a/java/src/com/google/idea/blaze/java/sync/model/BlazeJarLibrary.java b/java/src/com/google/idea/blaze/java/sync/model/BlazeJarLibrary.java
index 02bee44..3d0b128 100644
--- a/java/src/com/google/idea/blaze/java/sync/model/BlazeJarLibrary.java
+++ b/java/src/com/google/idea/blaze/java/sync/model/BlazeJarLibrary.java
@@ -17,7 +17,9 @@
 
 import com.google.common.base.Objects;
 import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.model.BlazeLibrary;
+import com.google.idea.blaze.base.model.LibraryKey;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
 import com.google.idea.blaze.java.libraries.JarCache;
 import com.google.idea.blaze.java.libraries.SourceJarManager;
@@ -35,12 +37,12 @@
 
   public final LibraryArtifact libraryArtifact;
 
-  public final RuleKey originatingRule;
+  public final TargetKey originatingTarget;
 
-  public BlazeJarLibrary(LibraryArtifact libraryArtifact, RuleKey originatingRule) {
+  public BlazeJarLibrary(LibraryArtifact libraryArtifact, TargetKey originatingTarget) {
     super(LibraryKey.fromJarFile(libraryArtifact.jarForIntellijLibrary()));
     this.libraryArtifact = libraryArtifact;
-    this.originatingRule = originatingRule;
+    this.originatingTarget = originatingTarget;
   }
 
   @Override
@@ -66,7 +68,7 @@
 
   @Override
   public int hashCode() {
-    return Objects.hashCode(super.hashCode(), libraryArtifact, originatingRule);
+    return Objects.hashCode(super.hashCode(), libraryArtifact, originatingTarget);
   }
 
   @Override
@@ -82,6 +84,6 @@
 
     return super.equals(other)
         && Objects.equal(libraryArtifact, that.libraryArtifact)
-        && Objects.equal(originatingRule, that.originatingRule);
+        && Objects.equal(originatingTarget, that.originatingTarget);
   }
 }
diff --git a/java/src/com/google/idea/blaze/java/sync/model/BlazeJavaImportResult.java b/java/src/com/google/idea/blaze/java/sync/model/BlazeJavaImportResult.java
index eb3c3c8..c2f904b 100644
--- a/java/src/com/google/idea/blaze/java/sync/model/BlazeJavaImportResult.java
+++ b/java/src/com/google/idea/blaze/java/sync/model/BlazeJavaImportResult.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
+import com.google.idea.blaze.base.model.LibraryKey;
 import java.io.Serializable;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
diff --git a/java/src/com/google/idea/blaze/java/sync/source/ManifestFilePackageReader.java b/java/src/com/google/idea/blaze/java/sync/source/ManifestFilePackageReader.java
index 96d1177..ab70400 100644
--- a/java/src/com/google/idea/blaze/java/sync/source/ManifestFilePackageReader.java
+++ b/java/src/com/google/idea/blaze/java/sync/source/ManifestFilePackageReader.java
@@ -16,7 +16,7 @@
 package com.google.idea.blaze.java.sync.source;
 
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.scope.BlazeContext;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
 import java.util.Map;
@@ -24,9 +24,9 @@
 
 class ManifestFilePackageReader extends JavaPackageReader {
 
-  private final Map<RuleKey, Map<ArtifactLocation, String>> manifestMap;
+  private final Map<TargetKey, Map<ArtifactLocation, String>> manifestMap;
 
-  public ManifestFilePackageReader(Map<RuleKey, Map<ArtifactLocation, String>> manifestMap) {
+  public ManifestFilePackageReader(Map<TargetKey, Map<ArtifactLocation, String>> manifestMap) {
     this.manifestMap = manifestMap;
   }
 
@@ -36,10 +36,10 @@
       BlazeContext context,
       ArtifactLocationDecoder artifactLocationDecoder,
       SourceArtifact sourceArtifact) {
-    Map<ArtifactLocation, String> manifestMapForRule =
-        manifestMap.get(sourceArtifact.originatingRule);
-    if (manifestMapForRule != null) {
-      return manifestMapForRule.get(sourceArtifact.artifactLocation);
+    Map<ArtifactLocation, String> manifestMapForTarget =
+        manifestMap.get(sourceArtifact.originatingTarget);
+    if (manifestMapForTarget != null) {
+      return manifestMapForTarget.get(sourceArtifact.artifactLocation);
     }
     return null;
   }
diff --git a/java/src/com/google/idea/blaze/java/sync/source/PackageManifestReader.java b/java/src/com/google/idea/blaze/java/sync/source/PackageManifestReader.java
index 96a0813..6338e14 100644
--- a/java/src/com/google/idea/blaze/java/sync/source/PackageManifestReader.java
+++ b/java/src/com/google/idea/blaze/java/sync/source/PackageManifestReader.java
@@ -24,7 +24,7 @@
 import com.google.idea.blaze.base.async.FutureUtil;
 import com.google.idea.blaze.base.filecache.FileDiffer;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.io.InputStreamProvider;
 import com.google.idea.blaze.base.prefetch.PrefetchService;
 import com.google.idea.blaze.base.scope.BlazeContext;
@@ -52,20 +52,20 @@
 
   private ImmutableMap<File, Long> fileDiffState;
 
-  private Map<File, RuleKey> fileToLabelMap = Maps.newHashMap();
-  private final Map<RuleKey, Map<ArtifactLocation, String>> manifestMap = Maps.newConcurrentMap();
+  private Map<File, TargetKey> fileToLabelMap = Maps.newHashMap();
+  private final Map<TargetKey, Map<ArtifactLocation, String>> manifestMap = Maps.newConcurrentMap();
 
   /** @return A map from java source absolute file path to declared package string. */
-  public Map<RuleKey, Map<ArtifactLocation, String>> readPackageManifestFiles(
+  public Map<TargetKey, Map<ArtifactLocation, String>> readPackageManifestFiles(
       Project project,
       BlazeContext context,
       ArtifactLocationDecoder decoder,
-      Map<RuleKey, ArtifactLocation> javaPackageManifests,
+      Map<TargetKey, ArtifactLocation> javaPackageManifests,
       ListeningExecutorService executorService) {
 
-    Map<File, RuleKey> fileToLabelMap = Maps.newHashMap();
-    for (Map.Entry<RuleKey, ArtifactLocation> entry : javaPackageManifests.entrySet()) {
-      RuleKey key = entry.getKey();
+    Map<File, TargetKey> fileToLabelMap = Maps.newHashMap();
+    for (Map.Entry<TargetKey, ArtifactLocation> entry : javaPackageManifests.entrySet()) {
+      TargetKey key = entry.getKey();
       File file = decoder.decode(entry.getValue());
       fileToLabelMap.put(file, key);
     }
@@ -95,7 +95,7 @@
               }));
     }
     for (File file : removedFiles) {
-      RuleKey key = this.fileToLabelMap.get(file);
+      TargetKey key = this.fileToLabelMap.get(file);
       if (key != null) {
         manifestMap.remove(key);
       }
diff --git a/java/src/com/google/idea/blaze/java/sync/source/SourceArtifact.java b/java/src/com/google/idea/blaze/java/sync/source/SourceArtifact.java
index 6c00f09..ff1d44c 100644
--- a/java/src/com/google/idea/blaze/java/sync/source/SourceArtifact.java
+++ b/java/src/com/google/idea/blaze/java/sync/source/SourceArtifact.java
@@ -16,28 +16,28 @@
 package com.google.idea.blaze.java.sync.source;
 
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 
 /** Pairing of rule and source artifact. */
 public class SourceArtifact {
-  public final RuleKey originatingRule;
+  public final TargetKey originatingTarget;
   public final ArtifactLocation artifactLocation;
 
-  public SourceArtifact(RuleKey originatingRule, ArtifactLocation artifactLocation) {
-    this.originatingRule = originatingRule;
+  public SourceArtifact(TargetKey originatingTarget, ArtifactLocation artifactLocation) {
+    this.originatingTarget = originatingTarget;
     this.artifactLocation = artifactLocation;
   }
 
-  public static Builder builder(RuleKey originatingRule) {
-    return new Builder(originatingRule);
+  public static Builder builder(TargetKey originatingTarget) {
+    return new Builder(originatingTarget);
   }
 
   static class Builder {
-    RuleKey originatingRule;
+    TargetKey originatingTarget;
     ArtifactLocation artifactLocation;
 
-    Builder(RuleKey originatingRule) {
-      this.originatingRule = originatingRule;
+    Builder(TargetKey originatingTarget) {
+      this.originatingTarget = originatingTarget;
     }
 
     public Builder setArtifactLocation(ArtifactLocation artifactLocation) {
@@ -50,7 +50,7 @@
     }
 
     public SourceArtifact build() {
-      return new SourceArtifact(originatingRule, artifactLocation);
+      return new SourceArtifact(originatingTarget, artifactLocation);
     }
   }
 }
diff --git a/java/src/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculator.java b/java/src/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculator.java
index 2fc3b21..805dc21 100644
--- a/java/src/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculator.java
+++ b/java/src/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculator.java
@@ -34,7 +34,7 @@
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.idea.blaze.base.async.executor.TransientExecutor;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.scope.BlazeContext;
@@ -85,14 +85,14 @@
       ArtifactLocationDecoder artifactLocationDecoder,
       Collection<WorkspacePath> rootDirectories,
       Collection<SourceArtifact> sources,
-      Map<RuleKey, ArtifactLocation> javaPackageManifests) {
+      Map<TargetKey, ArtifactLocation> javaPackageManifests) {
 
     ManifestFilePackageReader manifestFilePackageReader =
         Scope.push(
             context,
             (childContext) -> {
               childContext.push(new TimingScope("ReadPackageManifests"));
-              Map<RuleKey, Map<ArtifactLocation, String>> manifestMap =
+              Map<TargetKey, Map<ArtifactLocation, String>> manifestMap =
                   PackageManifestReader.getInstance()
                       .readPackageManifestFiles(
                           project,
diff --git a/java/src/com/google/idea/blaze/java/sync/workingset/JavaWorkingSet.java b/java/src/com/google/idea/blaze/java/sync/workingset/JavaWorkingSet.java
index 02c6881..a712009 100644
--- a/java/src/com/google/idea/blaze/java/sync/workingset/JavaWorkingSet.java
+++ b/java/src/com/google/idea/blaze/java/sync/workingset/JavaWorkingSet.java
@@ -18,7 +18,7 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.google.idea.blaze.base.model.primitives.WorkspaceRoot;
 import com.google.idea.blaze.base.sync.workspace.WorkingSet;
@@ -62,15 +62,15 @@
     this.modifiedJavaFileRelativePaths = modifiedJavaFileRelativePaths;
   }
 
-  public boolean isRuleInWorkingSet(RuleIdeInfo ruleIdeInfo) {
-    ArtifactLocation buildFile = ruleIdeInfo.buildFile;
+  public boolean isTargetInWorkingSet(TargetIdeInfo target) {
+    ArtifactLocation buildFile = target.buildFile;
     if (buildFile != null) {
       if (modifiedBuildFileRelativePaths.contains(buildFile.getRelativePath())) {
         return true;
       }
     }
 
-    for (ArtifactLocation artifactLocation : ruleIdeInfo.sources) {
+    for (ArtifactLocation artifactLocation : target.sources) {
       if (isInWorkingSet(artifactLocation)) {
         return true;
       }
diff --git a/java/src/com/google/idea/blaze/java/wizard2/BlazeProjectImportBuilder.java b/java/src/com/google/idea/blaze/java/wizard2/BlazeProjectImportBuilder.java
index 30445a4..14cabb7 100644
--- a/java/src/com/google/idea/blaze/java/wizard2/BlazeProjectImportBuilder.java
+++ b/java/src/com/google/idea/blaze/java/wizard2/BlazeProjectImportBuilder.java
@@ -18,6 +18,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.idea.blaze.base.settings.Blaze;
 import com.google.idea.blaze.base.wizard2.BlazeNewProjectBuilder;
+import com.intellij.compiler.CompilerWorkspaceConfiguration;
 import com.intellij.openapi.module.ModifiableModuleModel;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.project.Project;
@@ -74,6 +75,7 @@
       ModulesProvider modulesProvider,
       ModifiableArtifactModel artifactModel) {
     builder.commitToProject(project);
+    CompilerWorkspaceConfiguration.getInstance(project).MAKE_PROJECT_ON_SAVE = false;
     return ImmutableList.of();
   }
 
diff --git a/java/tests/integrationtests/com/google/idea/blaze/java/lang/build/JavaClassRenameTest.java b/java/tests/integrationtests/com/google/idea/blaze/java/lang/build/JavaClassRenameTest.java
index 04c83db..f7c1f49 100644
--- a/java/tests/integrationtests/com/google/idea/blaze/java/lang/build/JavaClassRenameTest.java
+++ b/java/tests/integrationtests/com/google/idea/blaze/java/lang/build/JavaClassRenameTest.java
@@ -17,6 +17,7 @@
 
 import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
 import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.psi.PsiJavaFile;
 import com.intellij.refactoring.rename.RenameProcessor;
 import org.junit.Test;
@@ -31,14 +32,15 @@
   public void testRenameJavaClass() {
     PsiJavaFile javaFile =
         (PsiJavaFile)
-            createPsiFile(
-                "com/google/foo/JavaClass.java",
+            workspace.createPsiFile(
+                new WorkspacePath("com/google/foo/JavaClass.java"),
                 "package com.google.foo;",
                 "public class JavaClass {}");
 
     BuildFile buildFile =
         createBuildFile(
-            "com/google/foo/BUILD", "java_library(name = \"ref2\", srcs = [\"JavaClass.java\"])");
+            new WorkspacePath("com/google/foo/BUILD"),
+            "java_library(name = \"ref2\", srcs = [\"JavaClass.java\"])");
 
     new RenameProcessor(getProject(), javaFile.getClasses()[0], "NewName", false, false).run();
 
diff --git a/java/tests/integrationtests/com/google/idea/blaze/java/lang/build/SafeDeleteTest.java b/java/tests/integrationtests/com/google/idea/blaze/java/lang/build/SafeDeleteTest.java
index bdee4a1..f4b38ee 100644
--- a/java/tests/integrationtests/com/google/idea/blaze/java/lang/build/SafeDeleteTest.java
+++ b/java/tests/integrationtests/com/google/idea/blaze/java/lang/build/SafeDeleteTest.java
@@ -17,6 +17,7 @@
 
 import com.google.idea.blaze.base.lang.buildfile.BuildFileIntegrationTestCase;
 import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.intellij.psi.PsiClass;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
@@ -34,12 +35,15 @@
   @Test
   public void testIndirectGlobReferencesNotIncluded() {
     PsiFile javaFile =
-        createPsiFile("com/google/Test.java", "package com.google;", "public class Test {}");
+        workspace.createPsiFile(
+            new WorkspacePath("com/google/Test.java"),
+            "package com.google;",
+            "public class Test {}");
 
     PsiClass javaClass = PsiUtils.findFirstChildOfClassRecursive(javaFile, PsiClass.class);
 
     createBuildFile(
-        "com/google/BUILD",
+        new WorkspacePath("com/google/BUILD"),
         "java_library(",
         "    name = 'lib'",
         "    srcs = glob(['*.java'])",
@@ -55,12 +59,15 @@
   @Test
   public void testDirectGlobReferencesIncluded() {
     PsiFile javaFile =
-        createPsiFile("com/google/Test.java", "package com.google;", "public class Test {}");
+        workspace.createPsiFile(
+            new WorkspacePath("com/google/Test.java"),
+            "package com.google;",
+            "public class Test {}");
 
     PsiClass javaClass = PsiUtils.findFirstChildOfClassRecursive(javaFile, PsiClass.class);
 
     createBuildFile(
-        "com/google/BUILD",
+        new WorkspacePath("com/google/BUILD"),
         "java_library(",
         "    name = 'lib'",
         "    srcs = glob(['Test.java'])",
diff --git a/java/tests/integrationtests/com/google/idea/blaze/java/sync/JavaSyncTest.java b/java/tests/integrationtests/com/google/idea/blaze/java/sync/JavaSyncTest.java
index 717e398..074cd27 100644
--- a/java/tests/integrationtests/com/google/idea/blaze/java/sync/JavaSyncTest.java
+++ b/java/tests/integrationtests/com/google/idea/blaze/java/sync/JavaSyncTest.java
@@ -17,11 +17,12 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import com.google.idea.blaze.base.ideinfo.JavaRuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
-import com.google.idea.blaze.base.ideinfo.RuleMapBuilder;
+import com.google.idea.blaze.base.ideinfo.JavaIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
+import com.google.idea.blaze.base.ideinfo.TargetMapBuilder;
 import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.google.idea.blaze.base.model.primitives.WorkspaceType;
 import com.google.idea.blaze.base.sync.BlazeSyncIntegrationTestCase;
 import com.google.idea.blaze.base.sync.BlazeSyncParams;
@@ -48,29 +49,29 @@
         "  //java/com/google:lib",
         "workspace_type: java");
 
-    createWorkspaceFile(
-        "java/com/google/ClassWithUniqueName1.java",
+    workspace.createFile(
+        new WorkspacePath("java/com/google/ClassWithUniqueName1.java"),
         "package com.google;",
         "public class ClassWithUniqueName1 {}");
 
-    createWorkspaceFile(
-        "java/com/google/ClassWithUniqueName2.java",
+    workspace.createFile(
+        new WorkspacePath("java/com/google/ClassWithUniqueName2.java"),
         "package com.google;",
         "public class ClassWithUniqueName2 {}");
 
-    RuleMap ruleMap =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMap targetMap =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("java/com/google/BUILD"))
                     .setLabel("//java/com/google:lib")
                     .setKind("java_library")
                     .addSource(sourceRoot("java/com/google/ClassWithUniqueName1.java"))
                     .addSource(sourceRoot("java/com/google/ClassWithUniqueName2.java"))
-                    .setJavaInfo(JavaRuleIdeInfo.builder()))
+                    .setJavaInfo(JavaIdeInfo.builder()))
             .build();
 
-    setRuleMap(ruleMap);
+    setTargetMap(targetMap);
 
     BlazeSyncParams syncParams =
         new BlazeSyncParams.Builder("Full Sync", BlazeSyncParams.SyncMode.FULL)
@@ -78,12 +79,12 @@
             .build();
     runBlazeSync(syncParams);
 
-    assertNoErrors();
+    errorCollector.assertNoIssues();
 
     BlazeProjectData blazeProjectData =
         BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData();
     assertThat(blazeProjectData).isNotNull();
-    assertThat(blazeProjectData.ruleMap).isEqualTo(ruleMap);
+    assertThat(blazeProjectData.targetMap).isEqualTo(targetMap);
     assertThat(blazeProjectData.workspaceLanguageSettings.getWorkspaceType())
         .isEqualTo(WorkspaceType.JAVA);
 
@@ -93,13 +94,13 @@
 
     BlazeContentEntry contentEntry = contentEntries.get(0);
     assertThat(contentEntry.contentRoot.getPath())
-        .isEqualTo(tempDirectory.getPath() + "/java/com/google");
+        .isEqualTo(workspaceRoot.fileForPath(new WorkspacePath("java/com/google")).getPath());
     assertThat(contentEntry.sources).hasSize(1);
 
     BlazeSourceDirectory sourceDir = contentEntry.sources.get(0);
     assertThat(sourceDir.getPackagePrefix()).isEqualTo("com.google");
     assertThat(sourceDir.getDirectory().getPath())
-        .isEqualTo(tempDirectory.getPath() + "/java/com/google");
+        .isEqualTo(workspaceRoot.fileForPath(new WorkspacePath("java/com/google")).getPath());
   }
 
   @Test
@@ -111,14 +112,20 @@
         "  //java/com/google:lib",
         "workspace_type: java");
 
-    createFile("java/com/google/Source.java", "package com.google;", "public class Source {}");
+    workspace.createFile(
+        new WorkspacePath("java/com/google/Source.java"),
+        "package com.google;",
+        "public class Source {}");
 
-    createFile("java/com/google/Other.java", "package com.google;", "public class Other {}");
+    workspace.createFile(
+        new WorkspacePath("java/com/google/Other.java"),
+        "package com.google;",
+        "public class Other {}");
 
-    RuleMap ruleMap =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMap targetMap =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("java/com/google/BUILD"))
                     .setLabel("//java/com/google:lib")
                     .setKind("java_library")
@@ -126,19 +133,19 @@
                     .addSource(sourceRoot("java/com/google/Other.java")))
             .build();
 
-    setRuleMap(ruleMap);
+    setTargetMap(targetMap);
 
     runBlazeSync(
         new BlazeSyncParams.Builder("Sync", SyncMode.INCREMENTAL)
             .addProjectViewTargets(true)
             .build());
 
-    assertNoErrors();
+    errorCollector.assertNoIssues();
 
     BlazeProjectData blazeProjectData =
         BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData();
     assertThat(blazeProjectData).isNotNull();
-    assertThat(blazeProjectData.ruleMap).isEqualTo(ruleMap);
+    assertThat(blazeProjectData.targetMap).isEqualTo(targetMap);
     assertThat(blazeProjectData.workspaceLanguageSettings.getWorkspaceType())
         .isEqualTo(WorkspaceType.JAVA);
   }
diff --git a/java/tests/unittests/com/google/idea/blaze/java/run/BlazeJavaRunProfileStateTest.java b/java/tests/unittests/com/google/idea/blaze/java/run/BlazeJavaRunProfileStateTest.java
index 51c2e6d..3f3aede 100644
--- a/java/tests/unittests/com/google/idea/blaze/java/run/BlazeJavaRunProfileStateTest.java
+++ b/java/tests/unittests/com/google/idea/blaze/java/run/BlazeJavaRunProfileStateTest.java
@@ -23,7 +23,7 @@
 import com.google.idea.blaze.base.command.BlazeCommandName;
 import com.google.idea.blaze.base.command.BlazeFlags;
 import com.google.idea.blaze.base.command.BuildFlagsProvider;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.model.primitives.Kind;
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
@@ -31,8 +31,8 @@
 import com.google.idea.blaze.base.run.BlazeCommandRunConfigurationType;
 import com.google.idea.blaze.base.run.confighandler.BlazeCommandGenericRunConfigurationHandlerProvider;
 import com.google.idea.blaze.base.run.confighandler.BlazeCommandRunConfigurationHandlerProvider;
-import com.google.idea.blaze.base.run.rulefinder.RuleFinder;
 import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState;
+import com.google.idea.blaze.base.run.targetfinder.TargetFinder;
 import com.google.idea.blaze.base.settings.Blaze.BuildSystem;
 import com.google.idea.blaze.base.settings.BlazeImportSettings;
 import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
@@ -65,7 +65,7 @@
 
     ExperimentService experimentService = new MockExperimentService();
     applicationServices.register(ExperimentService.class, experimentService);
-    applicationServices.register(RuleFinder.class, new MockRuleFinder());
+    applicationServices.register(TargetFinder.class, new MockTargetFinder());
     applicationServices.register(BlazeUserSettings.class, new BlazeUserSettings());
     registerExtensionPoint(BuildFlagsProvider.EP_NAME, BuildFlagsProvider.class);
     ExtensionPointImpl<BlazeCommandRunConfigurationHandlerProvider> handlerProviderEp =
@@ -137,19 +137,19 @@
                 BlazeFlags.getToolTagFlag(),
                 "--",
                 "//label:java_binary_rule",
-                "--debug"));
+                "--wrapper_script_flag=--debug"));
   }
 
-  private static class MockRuleFinder extends RuleFinder {
+  private static class MockTargetFinder extends TargetFinder {
     @Override
-    public List<RuleIdeInfo> findRules(Project project, Predicate<RuleIdeInfo> predicate) {
+    public List<TargetIdeInfo> findTargets(Project project, Predicate<TargetIdeInfo> predicate) {
       return null;
     }
 
     @Override
-    public RuleIdeInfo ruleForTarget(Project project, final Label target) {
-      RuleIdeInfo.Builder builder = RuleIdeInfo.builder().setLabel(target);
-      if (target.ruleName().toString().equals("java_binary_rule")) {
+    public TargetIdeInfo targetForLabel(Project project, final Label label) {
+      TargetIdeInfo.Builder builder = TargetIdeInfo.builder().setLabel(label);
+      if (label.targetName().toString().equals("java_binary_rule")) {
         builder.setKind(Kind.JAVA_BINARY);
       } else {
         builder.setKind(Kind.JAVA_TEST);
diff --git a/java/tests/unittests/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporterTest.java b/java/tests/unittests/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporterTest.java
index f6e59f8..76d49ac 100644
--- a/java/tests/unittests/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporterTest.java
+++ b/java/tests/unittests/com/google/idea/blaze/java/sync/importer/BlazeJavaWorkspaceImporterTest.java
@@ -27,17 +27,18 @@
 import com.google.idea.blaze.base.BlazeTestCase;
 import com.google.idea.blaze.base.async.executor.BlazeExecutor;
 import com.google.idea.blaze.base.async.executor.MockBlazeExecutor;
-import com.google.idea.blaze.base.ideinfo.AndroidRuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.AndroidIdeInfo;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.JavaRuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.JavaIdeInfo;
 import com.google.idea.blaze.base.ideinfo.JavaToolchainIdeInfo;
 import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
 import com.google.idea.blaze.base.ideinfo.ProtoLibraryLegacyInfo;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
-import com.google.idea.blaze.base.ideinfo.RuleMapBuilder;
 import com.google.idea.blaze.base.ideinfo.Tags;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
+import com.google.idea.blaze.base.ideinfo.TargetMapBuilder;
+import com.google.idea.blaze.base.model.LibraryKey;
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.model.primitives.LanguageClass;
 import com.google.idea.blaze.base.model.primitives.WorkspacePath;
@@ -69,7 +70,6 @@
 import com.google.idea.blaze.java.sync.model.BlazeJarLibrary;
 import com.google.idea.blaze.java.sync.model.BlazeJavaImportResult;
 import com.google.idea.blaze.java.sync.model.BlazeSourceDirectory;
-import com.google.idea.blaze.java.sync.model.LibraryKey;
 import com.google.idea.blaze.java.sync.source.JavaSourcePackageReader;
 import com.google.idea.blaze.java.sync.source.PackageManifestReader;
 import com.google.idea.blaze.java.sync.source.SourceArtifact;
@@ -109,16 +109,16 @@
   private ExtensionPointImpl<BlazeJavaSyncAugmenter> augmenters;
 
   private static class JdepsMock implements JdepsMap {
-    Map<RuleKey, List<String>> jdeps = Maps.newHashMap();
+    Map<TargetKey, List<String>> jdeps = Maps.newHashMap();
 
     @Nullable
     @Override
-    public List<String> getDependenciesForRule(RuleKey ruleKey) {
-      return jdeps.get(ruleKey);
+    public List<String> getDependenciesForTarget(TargetKey targetKey) {
+      return jdeps.get(targetKey);
     }
 
-    JdepsMock put(RuleKey ruleKey, List<String> values) {
-      jdeps.put(ruleKey, values);
+    JdepsMock put(TargetKey targetKey, List<String> values) {
+      jdeps.put(targetKey, values);
       return this;
     }
   }
@@ -167,20 +167,20 @@
   }
 
   BlazeJavaImportResult importWorkspace(
-      WorkspaceRoot workspaceRoot, RuleMapBuilder ruleMapBuilder, ProjectView projectView) {
+      WorkspaceRoot workspaceRoot, TargetMapBuilder targetMapBuilder, ProjectView projectView) {
 
     ProjectViewSet projectViewSet = ProjectViewSet.builder().add(projectView).build();
 
-    RuleMap ruleMap = ruleMapBuilder.build();
+    TargetMap targetMap = targetMapBuilder.build();
     JavaSourceFilter sourceFilter =
-        new JavaSourceFilter(project, workspaceRoot, projectViewSet, ruleMap);
+        new JavaSourceFilter(project, workspaceRoot, projectViewSet, targetMap);
     BlazeJavaWorkspaceImporter blazeWorkspaceImporter =
         new BlazeJavaWorkspaceImporter(
             project,
             workspaceRoot,
             projectViewSet,
             workspaceLanguageSettings,
-            ruleMap,
+            targetMap,
             sourceFilter,
             jdepsMap,
             workingSet,
@@ -193,7 +193,7 @@
   @Test
   public void testEmptyProject() {
     BlazeJavaImportResult result =
-        importWorkspace(workspaceRoot, RuleMapBuilder.builder(), ProjectView.builder().build());
+        importWorkspace(workspaceRoot, TargetMapBuilder.builder(), ProjectView.builder().build());
     errorCollector.assertNoIssues();
     assertTrue(result.contentEntries.isEmpty());
   }
@@ -207,30 +207,30 @@
                     .add(DirectoryEntry.include(new WorkspacePath("java/apps/example"))))
             .build();
 
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/apps/example:example_debug")
                     .setBuildFile(source("java/apps/example/BUILD"))
                     .setKind("android_binary")
                     .addSource(source("java/apps/example/MainActivity.java"))
                     .addSource(source("java/apps/example/subdir/SubdirHelper.java"))
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/apps/example/AndroidManifest.xml"))
                             .addResource(source("java/apps/example/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.apps.example"))
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(
                                         gen("java/apps/example/example_debug-ijar.jar"))
                                     .setClassJar(gen("java/apps/example/example_debug.jar")))));
 
-    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     errorCollector.assertNoIssues();
 
     assertEquals(1, result.buildOutputJars.size());
@@ -262,16 +262,16 @@
                     .add(DirectoryEntry.include(new WorkspacePath("java/example"))))
             .build();
 
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/example:lib")
                     .setBuildFile(source("java/example/BUILD"))
                     .setKind("java_library")
                     .addSource(source("java/example/Test.java"))
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("java/example/lib-ijar.jar"))
@@ -281,7 +281,7 @@
                                     .setInterfaceJar(gen("java/example/lib-gen.jar"))
                                     .setClassJar(gen("java/example/lib-gen.jar")))));
 
-    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     assertThat(
             result
                 .libraries
@@ -302,66 +302,66 @@
                     .add(DirectoryEntry.include(new WorkspacePath("java/apps/example"))))
             .build();
 
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/apps/example:example_debug")
                     .setBuildFile(source("java/apps/example/BUILD"))
                     .setKind("android_binary")
                     .addSource(source("java/apps/example/MainActivity.java"))
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/apps/example/AndroidManifest.xml"))
                             .addResource(source("java/apps/example/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.apps.example"))
                     .addDependency("//java/libraries/example:example")
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("java/apps/example/example_debug.jar"))
                                     .setClassJar(gen("java/apps/example/example_debug.jar")))))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/libraries/example:example")
                     .setBuildFile(source("java/libraries/example/BUILD"))
                     .setKind("java_library")
                     .addSource(source("java/libraries/example/SharedActivity.java"))
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/libraries/example/AndroidManifest.xml"))
                             .addResource(source("java/libraries/example/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.libraries.example"))
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("java/libraries/example/example.jar"))
                                     .setClassJar(gen("java/libraries/example/example.jar")))))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/com/dontimport:example_debug")
                     .setBuildFile(source("java/com/dontimport/BUILD"))
                     .setKind("android_binary")
                     .addSource(source("java/com/dontimport/MainActivity.java"))
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/com/dontimport/AndroidManifest.xml"))
                             .addResource(source("java/com/dontimport/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.dontimport"))
                     .addDependency("//java/com/dontimport:sometarget")
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("java/com/dontimport/example_debug.jar"))
                                     .setClassJar(gen("java/com/dontimport/example_debug.jar")))));
 
-    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     errorCollector.assertNoIssues();
 
     assertThat(result.contentEntries)
@@ -388,45 +388,45 @@
             .add(ListSection.builder(TestSourceSection.KEY).add(new Glob("javatests/*")))
             .build();
 
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/apps/example:example_debug")
                     .setBuildFile(source("java/apps/example/BUILD"))
                     .setKind("android_binary")
                     .addSource(source("java/apps/example/MainActivity.java"))
                     .addSource(source("java/apps/example/subdir/SubdirHelper.java"))
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/apps/example/AndroidManifest.xml"))
                             .addResource(source("java/apps/example/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.apps.example"))
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("java/apps/example/example_debug.jar"))
                                     .setClassJar(gen("java/apps/example/example_debug.jar")))))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//javatests/apps/example:example")
                     .setBuildFile(source("javatests/apps/example/BUILD"))
                     .setKind("android_test")
                     .addSource(source("javatests/apps/example/ExampleTests.java"))
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setResourceJavaPackage("com.google.android.apps.example"))
                     .addDependency("//java/apps/example:example_debug")
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("javatests/apps/example/example.jar"))
                                     .setClassJar(gen("javatests/apps/example/example.jar")))));
 
-    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     errorCollector.assertNoIssues();
 
     assertThat(result.contentEntries)
@@ -457,42 +457,42 @@
                     .add(DirectoryEntry.include(new WorkspacePath("javatests/apps/example"))))
             .build();
 
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/apps/example:example_debug")
                     .setBuildFile(source("java/apps/example/BUILD"))
                     .setKind("android_binary")
                     .addSource(source("java/apps/example/MainActivity.java"))
                     .addSource(source("java/apps/example/subdir/SubdirHelper.java"))
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/apps/example/AndroidManifest.xml"))
                             .addResource(gen("java/apps/example/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.apps.example"))
                     .addDependency("//thirdparty/some/library:library")
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("java/apps/example/example_debug.jar"))
                                     .setClassJar(gen("java/apps/example/example_debug.jar")))))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//thirdparty/some/library:library")
                     .setBuildFile(source("/thirdparty/some/library/BUILD"))
                     .setKind("java_import")
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("thirdparty/some/library.jar"))
                                     .setClassJar(gen("thirdparty/some/library.jar"))
                                     .setSourceJar(gen("thirdparty/some/library.srcjar")))));
 
-    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     errorCollector.assertNoIssues();
 
     BlazeJarLibrary library = findLibrary(result.libraries, "library.jar");
@@ -502,7 +502,7 @@
 
   /** Test a project with a java test rule */
   @Test
-  public void testJavaTestRule() {
+  public void testJavaTestTarget() {
     ProjectView projectView =
         ProjectView.builder()
             .add(
@@ -512,42 +512,42 @@
             .add(ListSection.builder(TestSourceSection.KEY).add(new Glob("javatests/*")))
             .build();
 
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/apps/example:example_debug")
                     .setBuildFile(source("java/apps/example/BUILD"))
                     .setKind("android_binary")
                     .addSource(source("java/apps/example/MainActivity.java"))
                     .addSource(source("java/apps/example/subdir/SubdirHelper.java"))
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/apps/example/AndroidManifest.xml"))
                             .addResource(source("java/apps/example/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.apps.example"))
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("java/apps/example/example_debug.jar"))
                                     .setClassJar(gen("java/apps/example/example_debug.jar")))))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//javatests/apps/example:example")
                     .setBuildFile(source("javatests/apps/example/BUILD"))
                     .setKind("java_test")
                     .addSource(source("javatests/apps/example/ExampleTests.java"))
                     .addDependency("//java/apps/example:example_debug")
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("javatests/apps/example/example.jar"))
                                     .setClassJar(gen("javatests/apps/example/example.jar")))));
 
-    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     errorCollector.assertNoIssues();
 
     assertThat(result.contentEntries)
@@ -580,37 +580,37 @@
                     .add(DirectoryEntry.include(new WorkspacePath("java/library/something"))))
             .build();
 
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/apps/example:example_debug")
                     .setBuildFile(source("java/apps/example/BUILD"))
                     .setKind("android_binary")
                     .addSource(source("java/apps/example/MainActivity.java"))
                     .addSource(source("java/apps/example/subdir/SubdirHelper.java"))
-                    .setJavaInfo(JavaRuleIdeInfo.builder())
+                    .setJavaInfo(JavaIdeInfo.builder())
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/apps/example/AndroidManifest.xml"))
                             .addResource(source("java/apps/example/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.apps.example"))
                     .addDependency("//java/library/something:something"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/library/something:something")
                     .setBuildFile(source("java/library/something/BUILD"))
                     .setKind("java_library")
                     .addSource(source("java/library/something/SomeJavaFile.java"))
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("java/library/something/something.jar"))
                                     .setClassJar(gen("java/library/something/something.jar")))));
 
-    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     errorCollector.assertNoIssues();
 
     assertThat(result.contentEntries)
@@ -639,30 +639,30 @@
                     .add(DirectoryEntry.include(new WorkspacePath("lib2"))))
             .build();
 
-    RuleMapBuilder response =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder response =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//lib:lib")
                     .setBuildFile(source("lib/BUILD"))
                     .setKind("java_library")
                     .addSource(source("lib/Lib.java"))
                     .addDependency("//lib2:lib2")
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("lib/lib.jar"))
                                     .setClassJar(gen("lib/lib.jar")))))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//lib2:lib2")
                     .setBuildFile(source("lib2/BUILD"))
                     .setKind("java_library")
                     .addSource(source("lib2/Lib2.java"))
                     .addTag("intellij-import-target-output")
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("lib2/lib2.jar"))
@@ -683,30 +683,30 @@
                     .add(DirectoryEntry.include(new WorkspacePath("lib2"))))
             .build();
 
-    RuleMapBuilder response =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder response =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//lib:lib")
                     .setBuildFile(source("lib/BUILD"))
                     .setKind("java_library")
                     .addSource(source("lib/Lib.java"))
                     .addDependency("//lib2:lib2")
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("lib/lib.jar"))
                                     .setClassJar(gen("lib/lib.jar")))))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//lib2:lib2")
                     .setBuildFile(source("lib2/BUILD"))
                     .setKind("java_library")
                     .addSource(source("lib2/Lib2.java"))
                     .addTag("aswb-import-as-library")
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("lib2/lib2.jar"))
@@ -727,35 +727,35 @@
                     .add(DirectoryEntry.include(new WorkspacePath("lib"))))
             .build();
 
-    RuleMapBuilder response =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder response =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//lib:libsource")
                     .setBuildFile(source("lib/BUILD"))
                     .setKind("java_library")
                     .addSource(source("lib/Source.java"))
-                    .setJavaInfo(JavaRuleIdeInfo.builder())
+                    .setJavaInfo(JavaIdeInfo.builder())
                     .addDependency("//lib:lib0")
                     .addDependency("//lib:lib1"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//lib:lib0")
                     .setBuildFile(source("lib/BUILD"))
                     .setKind("java_import")
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(source("lib/lib.jar"))
                                     .setClassJar(source("lib/lib.jar")))))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//lib:lib1")
                     .setBuildFile(source("lib/BUILD"))
                     .setKind("java_import")
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(source("lib/lib.jar"))
@@ -767,7 +767,7 @@
   }
 
   @Test
-  public void testRuleWithOnlyGeneratedSourcesIsAddedAsLibrary() {
+  public void testTargetWithOnlyGeneratedSourcesIsAddedAsLibrary() {
     ProjectView projectView =
         ProjectView.builder()
             .add(
@@ -775,37 +775,37 @@
                     .add(DirectoryEntry.include(new WorkspacePath("import"))))
             .build();
 
-    RuleMapBuilder response =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder response =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//import:lib")
                     .setBuildFile(source("import/BUILD"))
                     .setKind("android_library")
                     .addSource(source("import/Lib.java"))
-                    .setJavaInfo(JavaRuleIdeInfo.builder())
+                    .setJavaInfo(JavaIdeInfo.builder())
                     .addDependency("//import:import")
                     .addDependency("//import:import_android"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//import:import")
                     .setBuildFile(source("import/BUILD"))
                     .setKind("java_library")
                     .addSource(gen("import/GenSource.java"))
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("import/import.jar"))
                                     .setClassJar(gen("import/import.jar")))))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//import:import_android")
                     .setBuildFile(source("import/BUILD"))
                     .setKind("android_library")
                     .addSource(gen("import/GenSource.java"))
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("import/import_android.jar"))
@@ -819,7 +819,7 @@
   }
 
   @Test
-  public void testRuleWithMixedGeneratedSourcesAddsFilteredGenJar() {
+  public void testTargetWithMixedGeneratedSourcesAddsFilteredGenJar() {
     ProjectView projectView =
         ProjectView.builder()
             .add(
@@ -827,17 +827,17 @@
                     .add(DirectoryEntry.include(new WorkspacePath("import"))))
             .build();
 
-    RuleMapBuilder response =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder response =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//import:lib")
                     .setBuildFile(source("import/BUILD"))
                     .setKind("java_library")
                     .addSource(source("import/Import.java"))
                     .addSource(gen("import/Import.java"))
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .setFilteredGenJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("import/filtered-gen.jar")))));
@@ -848,7 +848,7 @@
   }
 
   @Test
-  public void testRuleWithOnlySourceJarAsSourceAddedAsLibrary() {
+  public void testTargetWithOnlySourceJarAsSourceAddedAsLibrary() {
     ProjectView projectView =
         ProjectView.builder()
             .add(
@@ -856,25 +856,25 @@
                     .add(DirectoryEntry.include(new WorkspacePath("import"))))
             .build();
 
-    RuleMapBuilder response =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder response =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//import:lib")
                     .setBuildFile(source("import/BUILD"))
                     .setKind("android_library")
                     .addSource(source("import/Lib.java"))
-                    .setJavaInfo(JavaRuleIdeInfo.builder())
+                    .setJavaInfo(JavaIdeInfo.builder())
                     .addDependency("//import:import")
                     .addDependency("//import:import_android"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//import:import")
                     .setBuildFile(source("import/BUILD"))
                     .setKind("java_library")
                     .addSource(gen("import/gen-src.jar"))
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("import/import.jar"))
@@ -898,24 +898,24 @@
                     .add(new Label("//import:import")))
             .build();
 
-    RuleMapBuilder response =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder response =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//import:lib")
                     .setBuildFile(source("import/BUILD"))
                     .setKind("java_library")
                     .addSource(source("import/Lib.java"))
-                    .setJavaInfo(JavaRuleIdeInfo.builder())
+                    .setJavaInfo(JavaIdeInfo.builder())
                     .addDependency("//import:import"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//import:import")
                     .setBuildFile(source("import/BUILD"))
                     .setKind("java_library")
                     .addSource(source("import/Import.java"))
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("import/import.jar"))
@@ -927,52 +927,52 @@
     assertThat(result.libraries).isNotEmpty();
   }
 
-  private RuleMapBuilder ruleMapForJdepsSuite() {
-    return RuleMapBuilder.builder()
-        .addRule(
-            RuleIdeInfo.builder()
+  private TargetMapBuilder targetMapForJdepsSuite() {
+    return TargetMapBuilder.builder()
+        .addTarget(
+            TargetIdeInfo.builder()
                 .setLabel("//java/apps/example:example_debug")
                 .setBuildFile(source("java/apps/example/BUILD"))
                 .setKind("java_library")
                 .addSource(source("java/apps/example/Test.java"))
-                .setJavaInfo(JavaRuleIdeInfo.builder())
+                .setJavaInfo(JavaIdeInfo.builder())
                 .addDependency("//thirdparty/a:a"))
-        .addRule(
-            RuleIdeInfo.builder()
+        .addTarget(
+            TargetIdeInfo.builder()
                 .setLabel("//thirdparty/a:a")
                 .setKind("java_library")
                 .addSource(source("thirdparty/a/A.java"))
                 .setBuildFile(source("third_party/a/BUILD"))
                 .addDependency("//thirdparty/b:b")
                 .setJavaInfo(
-                    JavaRuleIdeInfo.builder()
+                    JavaIdeInfo.builder()
                         .addJar(
                             LibraryArtifact.builder()
                                 .setInterfaceJar(gen("thirdparty/a.jar"))
                                 .setClassJar(gen("thirdparty/a.jar"))
                                 .setSourceJar(gen("thirdparty/a.srcjar")))))
-        .addRule(
-            RuleIdeInfo.builder()
+        .addTarget(
+            TargetIdeInfo.builder()
                 .setLabel("//thirdparty/b:b")
                 .setKind("java_library")
                 .addSource(source("thirdparty/b/B.java"))
                 .setBuildFile(source("third_party/b/BUILD"))
                 .addDependency("//thirdparty/c:c")
                 .setJavaInfo(
-                    JavaRuleIdeInfo.builder()
+                    JavaIdeInfo.builder()
                         .addJar(
                             LibraryArtifact.builder()
                                 .setInterfaceJar(gen("thirdparty/b.jar"))
                                 .setClassJar(gen("thirdparty/b.jar"))
                                 .setSourceJar(gen("thirdparty/b.srcjar")))))
-        .addRule(
-            RuleIdeInfo.builder()
+        .addTarget(
+            TargetIdeInfo.builder()
                 .setLabel("//thirdparty/c:c")
                 .setKind("java_library")
                 .addSource(source("thirdparty/c/C.java"))
                 .setBuildFile(source("third_party/c/BUILD"))
                 .setJavaInfo(
-                    JavaRuleIdeInfo.builder()
+                    JavaIdeInfo.builder()
                         .addJar(
                             LibraryArtifact.builder()
                                 .setInterfaceJar(gen("thirdparty/c.jar"))
@@ -989,12 +989,12 @@
                     .add(DirectoryEntry.include(new WorkspacePath("java/apps/example")))
                     .add(DirectoryEntry.include(new WorkspacePath("javatests/apps/example"))))
             .build();
-    RuleMapBuilder ruleMapBuilder = ruleMapForJdepsSuite();
+    TargetMapBuilder targetMapBuilder = targetMapForJdepsSuite();
     jdepsMap.put(
-        RuleKey.forPlainTarget(new Label("//java/apps/example:example_debug")),
+        TargetKey.forPlainTarget(new Label("//java/apps/example:example_debug")),
         Lists.newArrayList(jdepsPath("thirdparty/a.jar"), jdepsPath("thirdparty/c.jar")));
 
-    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     assertThat(
             result
                 .libraries
@@ -1015,7 +1015,7 @@
                     .add(DirectoryEntry.include(new WorkspacePath("java/apps/example")))
                     .add(DirectoryEntry.include(new WorkspacePath("javatests/apps/example"))))
             .build();
-    RuleMapBuilder ruleMapBuilder = ruleMapForJdepsSuite();
+    TargetMapBuilder targetMapBuilder = targetMapForJdepsSuite();
     workingSet =
         new JavaWorkingSet(
             workspaceRoot,
@@ -1025,7 +1025,7 @@
                 ImmutableList.of()),
             Predicate.isEqual("BUILD"));
 
-    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     assertThat(
             result
                 .libraries
@@ -1045,14 +1045,14 @@
                     .add(DirectoryEntry.include(new WorkspacePath("java/apps/example")))
                     .add(DirectoryEntry.include(new WorkspacePath("javatests/apps/example"))))
             .build();
-    RuleMapBuilder ruleMapBuilder = ruleMapForJdepsSuite();
+    TargetMapBuilder targetMapBuilder = targetMapForJdepsSuite();
     workingSet =
         new JavaWorkingSet(
             workspaceRoot,
             new WorkingSet(ImmutableList.of(), ImmutableList.of(), ImmutableList.of()),
             Predicate.isEqual("BUILD"));
 
-    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     assertThat(
             result
                 .libraries
@@ -1078,20 +1078,20 @@
                     .add(new Label("//java/apps/example:example")))
             .build();
 
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/apps/example:example")
                     .setBuildFile(source("java/apps/example/BUILD"))
                     .setKind("java_library")
                     .addSource(source("java/apps/example/Example.java"))
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder().setInterfaceJar(gen("example.jar")))));
 
-    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     errorCollector.assertNoIssues();
 
     assertThat(result.javaSourceFiles).isEmpty();
@@ -1109,21 +1109,21 @@
                     .add(DirectoryEntry.include(new WorkspacePath("java/apps/example"))))
             .build();
 
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/apps/example:example")
-                    .addTag(Tags.RULE_TAG_EXCLUDE_TARGET)
+                    .addTag(Tags.TARGET_TAG_EXCLUDE_TARGET)
                     .setBuildFile(source("java/apps/example/BUILD"))
                     .setKind("java_library")
                     .addSource(source("java/apps/example/Example.java"))
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder().setInterfaceJar(gen("example.jar")))));
 
-    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     errorCollector.assertNoIssues();
 
     assertThat(result.javaSourceFiles).isEmpty();
@@ -1139,26 +1139,26 @@
                     .add(DirectoryEntry.include(new WorkspacePath("java/example"))))
             .build();
 
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/example:liba")
                     .setBuildFile(source("java/example/BUILD"))
                     .setKind("java_library")
                     .addSource(source("java/example/Liba.java"))
-                    .setJavaInfo(JavaRuleIdeInfo.builder())
+                    .setJavaInfo(JavaIdeInfo.builder())
                     .addDependency("//thirdparty/proto/a:a"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/example:libb")
                     .setBuildFile(source("java/example/BUILD"))
                     .setKind("java_library")
                     .addSource(source("java/example/Libb.java"))
-                    .setJavaInfo(JavaRuleIdeInfo.builder())
+                    .setJavaInfo(JavaIdeInfo.builder())
                     .addDependency("//thirdparty/proto/b:b"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//thirdparty/proto/a:a")
                     .setBuildFile(source("/thirdparty/a/BUILD"))
                     .setKind("proto_library")
@@ -1172,8 +1172,8 @@
                                     .setInterfaceJar(gen("thirdparty/proto/a/liba-ijar.jar"))))
                     .addDependency("//thirdparty/proto/b:b")
                     .addDependency("//thirdparty/proto/c:c"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//thirdparty/proto/b:b")
                     .setBuildFile(source("/thirdparty/b/BUILD"))
                     .setKind("proto_library")
@@ -1186,8 +1186,8 @@
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("thirdparty/proto/b/libb-2-ijar.jar"))))
                     .addDependency("//thirdparty/proto/d:d"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//thirdparty/proto/c:c")
                     .setBuildFile(source("/thirdparty/c/BUILD"))
                     .setKind("proto_library")
@@ -1200,8 +1200,8 @@
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("thirdparty/proto/c/libc-ijar.jar"))))
                     .addDependency("//thirdparty/proto/d:d"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//thirdparty/proto/d:d")
                     .setBuildFile(source("/thirdparty/d/BUILD"))
                     .setKind("proto_library")
@@ -1222,9 +1222,9 @@
 
     // First test - make sure that jdeps is working
     jdepsMap.put(
-        RuleKey.forPlainTarget(new Label("//java/example:liba")),
+        TargetKey.forPlainTarget(new Label("//java/example:liba")),
         Lists.newArrayList(jdepsPath("thirdparty/proto/a/liba-ijar.jar")));
-    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     errorCollector.assertNoIssues();
     assertThat(result.libraries).hasSize(1);
     assertThat(findLibrary(result.libraries, "liba-ijar.jar")).isNotNull();
@@ -1240,7 +1240,7 @@
                 ImmutableList.of()),
             Predicate.isEqual("BUILD"));
 
-    result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     errorCollector.assertNoIssues();
 
     assertThat(result.libraries).hasSize(6);
@@ -1262,37 +1262,37 @@
                     .add(DirectoryEntry.include(new WorkspacePath(""))))
             .build();
 
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/apps/example:example_debug")
                     .setBuildFile(source("java/apps/example/BUILD"))
                     .setKind("android_binary")
                     .addSource(source("java/apps/example/MainActivity.java"))
                     .addSource(source("java/apps/example/subdir/SubdirHelper.java"))
-                    .setJavaInfo(JavaRuleIdeInfo.builder())
+                    .setJavaInfo(JavaIdeInfo.builder())
                     .setAndroidInfo(
-                        AndroidRuleIdeInfo.builder()
+                        AndroidIdeInfo.builder()
                             .setManifestFile(source("java/apps/example/AndroidManifest.xml"))
                             .addResource(source("java/apps/example/res"))
                             .setGenerateResourceClass(true)
                             .setResourceJavaPackage("com.google.android.apps.example"))
                     .addDependency("//java/library/something:something"))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/library/something:something")
                     .setBuildFile(source("java/library/something/BUILD"))
                     .setKind("java_library")
                     .addSource(source("java/library/something/SomeJavaFile.java"))
                     .setJavaInfo(
-                        JavaRuleIdeInfo.builder()
+                        JavaIdeInfo.builder()
                             .addJar(
                                 LibraryArtifact.builder()
                                     .setInterfaceJar(gen("java/library/something/something.jar"))
                                     .setClassJar(gen("java/library/something/something.jar")))));
 
-    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     errorCollector.assertNoIssues();
 
     assertThat(result.contentEntries)
@@ -1307,10 +1307,10 @@
   public void testLanguageLevelIsReadFromToolchain() {
     ProjectView projectView = ProjectView.builder().build();
 
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java:toolchain")
                     .setBuildFile(source("java/BUILD"))
                     .setKind("java_toolchain")
@@ -1319,29 +1319,25 @@
                             .setSourceVersion("8")
                             .setTargetVersion("8")));
 
-    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     assertThat(result.sourceVersion).isEqualTo("8");
   }
 
   @Test
   public void testSyncAugmenter() {
     augmenters.registerExtension(
-        new BlazeJavaSyncAugmenter.Adapter() {
+        new BlazeJavaSyncAugmenter() {
           @Override
-          public boolean isActive(WorkspaceLanguageSettings workspaceLanguageSettings) {
-            return true;
-          }
-
-          @Override
-          public void addJarsForSourceRule(
-              RuleIdeInfo rule,
+          public void addJarsForSourceTarget(
+              WorkspaceLanguageSettings workspaceLanguageSettings,
+              TargetIdeInfo target,
               Collection<BlazeJarLibrary> jars,
               Collection<BlazeJarLibrary> genJars) {
-            if (rule.label.equals(new Label("//java/example:source"))) {
+            if (target.key.label.equals(new Label("//java/example:source"))) {
               jars.add(
                   new BlazeJarLibrary(
                       LibraryArtifact.builder().setInterfaceJar(gen("source.jar")).build(),
-                      rule.key));
+                      target.key));
             }
           }
         });
@@ -1353,25 +1349,25 @@
                     .add(DirectoryEntry.include(new WorkspacePath("java/example"))))
             .build();
 
-    RuleMapBuilder ruleMapBuilder =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMapBuilder targetMapBuilder =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/example:source")
                     .setBuildFile(source("java/example/BUILD"))
                     .setKind("java_library")
                     .addSource(source("Source.java"))
                     .addDependency("//java/lib:lib")
-                    .setJavaInfo(JavaRuleIdeInfo.builder()))
-            .addRule(
-                RuleIdeInfo.builder()
+                    .setJavaInfo(JavaIdeInfo.builder()))
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setLabel("//java/lib:lib")
                     .setBuildFile(source("java/lib/BUILD"))
                     .setKind("java_library")
                     .addSource(source("Lib.java"))
-                    .setJavaInfo(JavaRuleIdeInfo.builder()));
+                    .setJavaInfo(JavaIdeInfo.builder()));
 
-    BlazeJavaImportResult result = importWorkspace(workspaceRoot, ruleMapBuilder, projectView);
+    BlazeJavaImportResult result = importWorkspace(workspaceRoot, targetMapBuilder, projectView);
     assertThat(
             result
                 .libraries
diff --git a/java/tests/unittests/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculatorTest.java b/java/tests/unittests/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculatorTest.java
index 684cd7f..ccd035a 100644
--- a/java/tests/unittests/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculatorTest.java
+++ b/java/tests/unittests/com/google/idea/blaze/java/sync/source/SourceDirectoryCalculatorTest.java
@@ -25,7 +25,7 @@
 import com.google.idea.blaze.base.async.executor.BlazeExecutor;
 import com.google.idea.blaze.base.async.executor.MockBlazeExecutor;
 import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.io.FileAttributeProvider;
 import com.google.idea.blaze.base.io.InputStreamProvider;
 import com.google.idea.blaze.base.model.primitives.ExecutionRootPath;
@@ -67,7 +67,7 @@
 @RunWith(JUnit4.class)
 public class SourceDirectoryCalculatorTest extends BlazeTestCase {
 
-  private static final ImmutableMap<RuleKey, ArtifactLocation> NO_MANIFESTS = ImmutableMap.of();
+  private static final ImmutableMap<TargetKey, ArtifactLocation> NO_MANIFESTS = ImmutableMap.of();
   private static final Label LABEL = new Label("//fake:label");
 
   private MockInputStreamProvider mockInputStreamProvider;
@@ -149,7 +149,7 @@
         "/root/java/com/google/Bla.java", "package com.google;\n public class Bla {}");
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/Bla.java")
@@ -182,7 +182,7 @@
         "/root/java/com/google/Bla.java", "package com.google;\n public class Bla {}");
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/Bla.java")
@@ -219,13 +219,13 @@
             "package com.google.subpackage;\n public class Bla {}");
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/Bla.java")
                         .setIsSource(true))
                 .build(),
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/subpackage/Bla.java")
@@ -266,19 +266,19 @@
             "package com.google.idea.blaze.plugin;\n public class Plugin {}");
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/idea/blaze/plugin/run/Run.java")
                         .setIsSource(true))
                 .build(),
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/idea/blaze/plugin/sync/Sync.java")
                         .setIsSource(true))
                 .build(),
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/idea/blaze/plugin/Plugin.java")
@@ -317,19 +317,19 @@
             "package com.google.idea.blaze.incorrect;\n public class Incorrect {}");
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/idea/blaze/plugin/run/Run.java")
                         .setIsSource(true))
                 .build(),
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/idea/blaze/plugin/sync/Sync.java")
                         .setIsSource(true))
                 .build(),
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/idea/blaze/Incorrect.java")
@@ -372,13 +372,13 @@
             "package com.google.different;\n public class Bla {}");
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/Bla.java")
                         .setIsSource(true))
                 .build(),
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/subpackage/Bla.java")
@@ -418,13 +418,13 @@
             "package com.google.subpackage;\n public class Bla {}");
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/Bla.java")
                         .setIsSource(true))
                 .build(),
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/subpackage/Bla.java")
@@ -464,13 +464,13 @@
             "package com.google.different;\n public class Bla {}");
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/subpackage/Bla.java")
                         .setIsSource(true))
                 .build(),
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/Bla.java")
@@ -507,7 +507,7 @@
         "/root/java/com/google/Bla.java", "package com.google;\n public class Bla {}");
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/Bla.java")
@@ -540,7 +540,7 @@
         "/root/java/com/google/Bla.java", "package com.facebook;\n public class Bla {}");
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/Bla.java")
@@ -573,7 +573,7 @@
         "/root/java/com/org/foo/Bla.java", "package com.facebook;\n public class Bla {}");
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/org/foo/Bla.java")
@@ -610,7 +610,7 @@
         "/root/java/com/facebook/Bla.java", "package com.facebook;\n public class Bla {}");
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/facebook/Bla.java")
@@ -636,7 +636,7 @@
         "/root/java/com/facebook/Bla.java", "package com.facebook;\n public class Bla {}");
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/facebook/Bla.java")
@@ -659,7 +659,7 @@
     mockInputStreamProvider.addFile("/root/java/com/google/Bla.java", "public class Bla {}");
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/Bla.java")
@@ -688,25 +688,25 @@
         .addFile("/root/java/com/google/Bla3.java", "package com.google;\n public class Bla3 {}");
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/Bla.java")
                         .setIsSource(true))
                 .build(),
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/Bla2.java")
                         .setIsSource(true))
                 .build(),
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/Bla3.java")
                         .setIsSource(true))
                 .build(),
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/Foo.java")
@@ -746,19 +746,19 @@
             "package com.google.subpackage.subsubpackage;\n public class Bla {}");
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/Bla.java")
                         .setIsSource(true))
                 .build(),
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/subpackage/Bla.java")
                         .setIsSource(true))
                 .build(),
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/subpackage/subsubpackage/Bla.java")
@@ -800,13 +800,13 @@
             "package com.google.packagewrong1;\n public class Bla {}");
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/package0/Bla.java")
                         .setIsSource(true))
                 .build(),
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/package1/Bla.java")
@@ -853,14 +853,14 @@
             "package com.google.android.chimera.container;\n public class FileApkUtils {}");
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath(
                             "java/com/google/android/chimera/internal/Preconditions.java")
                         .setIsSource(true))
                 .build(),
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath(
@@ -902,19 +902,19 @@
                 .setIsSource(true)
                 .build()),
         ImmutableList.of("com.google"));
-    ImmutableMap<RuleKey, ArtifactLocation> manifests =
-        ImmutableMap.<RuleKey, ArtifactLocation>builder()
+    ImmutableMap<TargetKey, ArtifactLocation> manifests =
+        ImmutableMap.<TargetKey, ArtifactLocation>builder()
             .put(
-                RuleKey.forPlainTarget(LABEL),
+                TargetKey.forPlainTarget(LABEL),
                 ArtifactLocation.builder()
                     .setRelativePath("java/com/test.manifest")
                     .setIsSource(true)
                     .build())
             .build();
-    Map<RuleKey, Map<ArtifactLocation, String>> manifestMap =
+    Map<TargetKey, Map<ArtifactLocation, String>> manifestMap =
         readPackageManifestFiles(manifests, getDecoder("/root"));
 
-    assertThat(manifestMap.get(RuleKey.forPlainTarget(LABEL)))
+    assertThat(manifestMap.get(TargetKey.forPlainTarget(LABEL)))
         .containsEntry(
             ArtifactLocation.builder()
                 .setRelativePath("java/com/google/Bla.java")
@@ -929,19 +929,19 @@
         "/root/java/com/test.manifest",
         ImmutableList.of("java/com/google/Bla.java"),
         ImmutableList.of("com.google"));
-    ImmutableMap<RuleKey, ArtifactLocation> manifests =
-        ImmutableMap.<RuleKey, ArtifactLocation>builder()
+    ImmutableMap<TargetKey, ArtifactLocation> manifests =
+        ImmutableMap.<TargetKey, ArtifactLocation>builder()
             .put(
-                RuleKey.forPlainTarget(LABEL),
+                TargetKey.forPlainTarget(LABEL),
                 ArtifactLocation.builder()
                     .setRelativePath("java/com/test.manifest")
                     .setIsSource(true)
                     .build())
             .build();
-    Map<RuleKey, Map<ArtifactLocation, String>> manifestMap =
+    Map<TargetKey, Map<ArtifactLocation, String>> manifestMap =
         readPackageManifestFiles(manifests, getDecoder("/root"));
 
-    assertThat(manifestMap.get(RuleKey.forPlainTarget(LABEL)))
+    assertThat(manifestMap.get(TargetKey.forPlainTarget(LABEL)))
         .containsEntry(
             ArtifactLocation.builder()
                 .setRelativePath("java/com/google/Bla.java")
@@ -960,41 +960,41 @@
         "/root/java/com/test2.manifest",
         ImmutableList.of("java/com/google/Bla.java", "java/com/google/other/Temp.java"),
         ImmutableList.of("com.google", "com.google.other"));
-    ImmutableMap<RuleKey, ArtifactLocation> manifests =
-        ImmutableMap.<RuleKey, ArtifactLocation>builder()
+    ImmutableMap<TargetKey, ArtifactLocation> manifests =
+        ImmutableMap.<TargetKey, ArtifactLocation>builder()
             .put(
-                RuleKey.forPlainTarget(new Label("//a:a")),
+                TargetKey.forPlainTarget(new Label("//a:a")),
                 ArtifactLocation.builder()
                     .setRelativePath("java/com/test.manifest")
                     .setIsSource(true)
                     .build())
             .put(
-                RuleKey.forPlainTarget(new Label("//b:b")),
+                TargetKey.forPlainTarget(new Label("//b:b")),
                 ArtifactLocation.builder()
                     .setRelativePath("java/com/test2.manifest")
                     .setIsSource(true)
                     .build())
             .build();
-    Map<RuleKey, Map<ArtifactLocation, String>> manifestMap =
+    Map<TargetKey, Map<ArtifactLocation, String>> manifestMap =
         readPackageManifestFiles(manifests, getDecoder("/root"));
 
     assertThat(manifestMap).hasSize(2);
 
-    assertThat(manifestMap.get(RuleKey.forPlainTarget(new Label("//a:a"))))
+    assertThat(manifestMap.get(TargetKey.forPlainTarget(new Label("//a:a"))))
         .containsEntry(
             ArtifactLocation.builder()
                 .setRelativePath("java/com/google/Bla.java")
                 .setIsSource(true)
                 .build(),
             "com.google");
-    assertThat(manifestMap.get(RuleKey.forPlainTarget(new Label("//a:a"))))
+    assertThat(manifestMap.get(TargetKey.forPlainTarget(new Label("//a:a"))))
         .containsEntry(
             ArtifactLocation.builder()
                 .setRelativePath("java/com/google/Foo.java")
                 .setIsSource(true)
                 .build(),
             "com.google.subpackage");
-    assertThat(manifestMap.get(RuleKey.forPlainTarget(new Label("//b:b"))))
+    assertThat(manifestMap.get(TargetKey.forPlainTarget(new Label("//b:b"))))
         .containsEntry(
             ArtifactLocation.builder()
                 .setRelativePath("java/com/google/other/Temp.java")
@@ -1014,10 +1014,10 @@
         "/root/java/com/google/subpackage/Bla.java",
         "package com.google.different;\n public class Bla {}");
 
-    ImmutableMap<RuleKey, ArtifactLocation> manifests =
-        ImmutableMap.<RuleKey, ArtifactLocation>builder()
+    ImmutableMap<TargetKey, ArtifactLocation> manifests =
+        ImmutableMap.<TargetKey, ArtifactLocation>builder()
             .put(
-                RuleKey.forPlainTarget(LABEL),
+                TargetKey.forPlainTarget(LABEL),
                 ArtifactLocation.builder()
                     .setRelativePath("java/com/test.manifest")
                     .setIsSource(true)
@@ -1026,19 +1026,19 @@
 
     List<SourceArtifact> sourceArtifacts =
         ImmutableList.of(
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/Bla.java")
                         .setIsSource(true))
                 .build(),
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/Foo.java")
                         .setIsSource(true))
                 .build(),
-            SourceArtifact.builder(RuleKey.forPlainTarget(LABEL))
+            SourceArtifact.builder(TargetKey.forPlainTarget(LABEL))
                 .setArtifactLocation(
                     ArtifactLocation.builder()
                         .setRelativePath("java/com/google/subpackage/Bla.java")
@@ -1111,7 +1111,8 @@
             root,
             ImmutableList.of(root),
             new ExecutionRootPath("out/crosstool/bin"),
-            new ExecutionRootPath("out/crosstool/gen"));
+            new ExecutionRootPath("out/crosstool/gen"),
+            null);
     return new ArtifactLocationDecoderImpl(
         roots, new WorkspacePathResolverImpl(workspaceRoot, roots));
   }
@@ -1145,8 +1146,8 @@
     }
   }
 
-  private Map<RuleKey, Map<ArtifactLocation, String>> readPackageManifestFiles(
-      Map<RuleKey, ArtifactLocation> manifests, ArtifactLocationDecoder decoder) {
+  private Map<TargetKey, Map<ArtifactLocation, String>> readPackageManifestFiles(
+      Map<TargetKey, ArtifactLocation> manifests, ArtifactLocationDecoder decoder) {
     return PackageManifestReader.getInstance()
         .readPackageManifestFiles(
             project, context, decoder, manifests, MoreExecutors.sameThreadExecutor());
diff --git a/plugin_dev/src/com/google/idea/blaze/plugin/IntellijPluginRule.java b/plugin_dev/src/com/google/idea/blaze/plugin/IntellijPluginRule.java
index 5e5668d..c669c8f 100644
--- a/plugin_dev/src/com/google/idea/blaze/plugin/IntellijPluginRule.java
+++ b/plugin_dev/src/com/google/idea/blaze/plugin/IntellijPluginRule.java
@@ -15,25 +15,26 @@
  */
 package com.google.idea.blaze.plugin;
 
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.model.primitives.Kind;
 
-/** Utility methods for intellij_plugin blaze rules */
+/** Utility methods for intellij_plugin blaze targets */
 public class IntellijPluginRule {
 
-  public static final String RULE_TAG_IJ_PLUGIN = "intellij-plugin";
-  public static final String RULE_TAG_IJ_PLUGIN_BUNDLE = "intellij-plugin-bundle";
+  public static final String TARGET_TAG_IJ_PLUGIN = "intellij-plugin";
+  public static final String TARGET_TAG_IJ_PLUGIN_BUNDLE = "intellij-plugin-bundle";
 
-  public static boolean isPluginRule(RuleIdeInfo rule) {
-    return isPluginBundle(rule) || isSinglePluginRule(rule);
+  public static boolean isPluginTarget(TargetIdeInfo target) {
+    return isPluginBundle(target) || isSinglePluginTarget(target);
   }
 
-  public static boolean isPluginBundle(RuleIdeInfo rule) {
-    return rule.kindIsOneOf(Kind.JAVA_LIBRARY) && rule.tags.contains(RULE_TAG_IJ_PLUGIN_BUNDLE);
+  public static boolean isPluginBundle(TargetIdeInfo target) {
+    return target.kindIsOneOf(Kind.JAVA_LIBRARY)
+        && target.tags.contains(TARGET_TAG_IJ_PLUGIN_BUNDLE);
   }
 
-  public static boolean isSinglePluginRule(RuleIdeInfo rule) {
-    return rule.kindIsOneOf(Kind.JAVA_IMPORT) && rule.tags.contains(RULE_TAG_IJ_PLUGIN);
+  public static boolean isSinglePluginTarget(TargetIdeInfo target) {
+    return target.kindIsOneOf(Kind.JAVA_IMPORT) && target.tags.contains(TARGET_TAG_IJ_PLUGIN);
   }
 
 }
diff --git a/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfiguration.java b/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfiguration.java
index 57e0e9c..00cbccb 100644
--- a/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfiguration.java
+++ b/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfiguration.java
@@ -24,16 +24,18 @@
 import com.google.idea.blaze.base.command.BlazeCommand;
 import com.google.idea.blaze.base.command.BlazeCommandName;
 import com.google.idea.blaze.base.command.BlazeFlags;
-import com.google.idea.blaze.base.ideinfo.JavaRuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.Dependency;
+import com.google.idea.blaze.base.ideinfo.Dependency.DependencyType;
+import com.google.idea.blaze.base.ideinfo.JavaIdeInfo;
 import com.google.idea.blaze.base.ideinfo.LibraryArtifact;
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.model.primitives.TargetExpression;
 import com.google.idea.blaze.base.projectview.ProjectViewSet;
 import com.google.idea.blaze.base.run.BlazeConfigurationNameBuilder;
 import com.google.idea.blaze.base.run.BlazeRunConfiguration;
-import com.google.idea.blaze.base.run.rulefinder.RuleFinder;
+import com.google.idea.blaze.base.run.targetfinder.TargetFinder;
 import com.google.idea.blaze.base.settings.Blaze;
 import com.google.idea.blaze.base.sync.data.BlazeProjectDataManager;
 import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
@@ -117,15 +119,15 @@
       Project project,
       ConfigurationFactory factory,
       String name,
-      @Nullable RuleIdeInfo initialRule) {
+      @Nullable TargetIdeInfo initialTarget) {
     super(project, factory, name);
     this.buildSystem = Blaze.buildSystemName(project);
     Sdk projectSdk = ProjectRootManager.getInstance(project).getProjectSdk();
     if (IdeaJdkHelper.isIdeaJdk(projectSdk)) {
       pluginSdk = projectSdk;
     }
-    if (initialRule != null) {
-      target = initialRule.label;
+    if (initialTarget != null) {
+      target = initialTarget.key.label;
     }
   }
 
@@ -151,42 +153,46 @@
     if (blazeProjectData == null) {
       throw new ExecutionException("Not synced yet, please sync project");
     }
-    RuleIdeInfo rule = RuleFinder.getInstance().ruleForTarget(getProject(), getTarget());
-    if (rule == null) {
+    TargetIdeInfo target = TargetFinder.getInstance().targetForLabel(getProject(), getTarget());
+    if (target == null) {
       throw new ExecutionException(
-          buildSystem + " rule '" + getTarget() + "' not imported during sync");
+          buildSystem + " target '" + getTarget() + "' not imported during sync");
     }
-    return IntellijPluginRule.isPluginBundle(rule)
-        ? findBundledJars(blazeProjectData.artifactLocationDecoder, rule)
-        : ImmutableList.of(findPluginJar(blazeProjectData.artifactLocationDecoder, rule));
+    return IntellijPluginRule.isPluginBundle(target)
+        ? findBundledJars(blazeProjectData.artifactLocationDecoder, target)
+        : ImmutableList.of(findPluginJar(blazeProjectData.artifactLocationDecoder, target));
   }
 
   private ImmutableList<File> findBundledJars(
-      ArtifactLocationDecoder artifactLocationDecoder, RuleIdeInfo rule) throws ExecutionException {
+      ArtifactLocationDecoder artifactLocationDecoder, TargetIdeInfo target)
+      throws ExecutionException {
     ImmutableList.Builder<File> jars = ImmutableList.builder();
-    for (Label dep : rule.dependencies) {
-      RuleIdeInfo depRule = RuleFinder.getInstance().ruleForTarget(getProject(), dep);
-      if (depRule != null && IntellijPluginRule.isSinglePluginRule(depRule)) {
-        jars.add(findPluginJar(artifactLocationDecoder, depRule));
+    for (Dependency dep : target.dependencies) {
+      if (dep.dependencyType == DependencyType.COMPILE_TIME && dep.targetKey.isPlainTarget()) {
+        TargetIdeInfo depTarget =
+            TargetFinder.getInstance().targetForLabel(getProject(), dep.targetKey.label);
+        if (depTarget != null && IntellijPluginRule.isSinglePluginTarget(depTarget)) {
+          jars.add(findPluginJar(artifactLocationDecoder, depTarget));
+        }
       }
     }
     return jars.build();
   }
 
-  private File findPluginJar(ArtifactLocationDecoder artifactLocationDecoder, RuleIdeInfo rule)
+  private File findPluginJar(ArtifactLocationDecoder artifactLocationDecoder, TargetIdeInfo target)
       throws ExecutionException {
-    JavaRuleIdeInfo javaRuleIdeInfo = rule.javaRuleIdeInfo;
-    if (!IntellijPluginRule.isSinglePluginRule(rule) || javaRuleIdeInfo == null) {
+    JavaIdeInfo javaIdeInfo = target.javaIdeInfo;
+    if (!IntellijPluginRule.isSinglePluginTarget(target) || javaIdeInfo == null) {
       throw new ExecutionException(
-          buildSystem + " rule '" + rule.label + "' is not a valid intellij_plugin rule");
+          buildSystem + " target '" + target + "' is not a valid intellij_plugin target");
     }
-    Collection<LibraryArtifact> jars = javaRuleIdeInfo.jars;
-    if (javaRuleIdeInfo.jars.size() > 1) {
-      throw new ExecutionException("Invalid IntelliJ plugin rule: it has multiple output jars");
+    Collection<LibraryArtifact> jars = javaIdeInfo.jars;
+    if (javaIdeInfo.jars.size() > 1) {
+      throw new ExecutionException("Invalid IntelliJ plugin target: it has multiple output jars");
     }
     LibraryArtifact artifact = jars.isEmpty() ? null : jars.iterator().next();
     if (artifact == null || artifact.classJar == null) {
-      throw new ExecutionException("No output plugin jar found for '" + rule.label + "'");
+      throw new ExecutionException("No output plugin jar found for '" + target + "'");
     }
     return artifactLocationDecoder.decode(artifact.classJar);
   }
@@ -321,15 +327,15 @@
   public void checkConfiguration() throws RuntimeConfigurationException {
     super.checkConfiguration();
 
-    Label target = getTarget();
-    if (target == null) {
+    Label label = getTarget();
+    if (label == null) {
       throw new RuntimeConfigurationError("Select a target to run");
     }
-    RuleIdeInfo rule = RuleFinder.getInstance().ruleForTarget(getProject(), target);
-    if (rule == null) {
+    TargetIdeInfo target = TargetFinder.getInstance().targetForLabel(getProject(), label);
+    if (target == null) {
       throw new RuntimeConfigurationError("The selected target does not exist.");
     }
-    if (!IntellijPluginRule.isPluginRule(rule)) {
+    if (!IntellijPluginRule.isPluginTarget(target)) {
       throw new RuntimeConfigurationError("The selected target is not an intellij_plugin");
     }
     if (!IdeaJdkHelper.isIdeaJdk(pluginSdk)) {
@@ -424,11 +430,11 @@
 
   @Override
   public BlazeIntellijPluginConfigurationSettingsEditor getConfigurationEditor() {
-    List<RuleIdeInfo> javaRules =
-        RuleFinder.getInstance().findRules(getProject(), IntellijPluginRule::isPluginRule);
+    List<TargetIdeInfo> javaTargets =
+        TargetFinder.getInstance().findTargets(getProject(), IntellijPluginRule::isPluginTarget);
     List<Label> javaLabels = Lists.newArrayList();
-    for (RuleIdeInfo rule : javaRules) {
-      javaLabels.add(rule.label);
+    for (TargetIdeInfo target : javaTargets) {
+      javaLabels.add(target.key.label);
     }
     return new BlazeIntellijPluginConfigurationSettingsEditor(buildSystem, javaLabels);
   }
diff --git a/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfigurationType.java b/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfigurationType.java
index 5ff8d45..f2e7772 100644
--- a/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfigurationType.java
+++ b/plugin_dev/src/com/google/idea/blaze/plugin/run/BlazeIntellijPluginConfigurationType.java
@@ -15,13 +15,13 @@
  */
 package com.google.idea.blaze.plugin.run;
 
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleKey;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetKey;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.Label;
 import com.google.idea.blaze.base.model.primitives.WorkspaceType;
 import com.google.idea.blaze.base.run.BlazeRunConfigurationFactory;
-import com.google.idea.blaze.base.run.rulefinder.RuleFinder;
+import com.google.idea.blaze.base.run.targetfinder.TargetFinder;
 import com.google.idea.blaze.base.settings.Blaze;
 import com.google.idea.blaze.plugin.IntellijPluginRule;
 import com.intellij.diagnostic.VMOptions;
@@ -50,13 +50,13 @@
 
   static class BlazeIntellijPluginRunConfigurationFactory extends BlazeRunConfigurationFactory {
     @Override
-    public boolean handlesTarget(Project project, BlazeProjectData blazeProjectData, Label target) {
+    public boolean handlesTarget(Project project, BlazeProjectData blazeProjectData, Label label) {
       if (!blazeProjectData.workspaceLanguageSettings.isWorkspaceType(
           WorkspaceType.INTELLIJ_PLUGIN)) {
         return false;
       }
-      RuleIdeInfo rule = blazeProjectData.ruleMap.get(RuleKey.forPlainTarget(target));
-      return rule != null && IntellijPluginRule.isPluginRule(rule);
+      TargetIdeInfo target = blazeProjectData.targetMap.get(TargetKey.forPlainTarget(label));
+      return target != null && IntellijPluginRule.isPluginTarget(target);
     }
 
     @Override
@@ -99,7 +99,8 @@
               project,
               this,
               "Unnamed",
-              RuleFinder.getInstance().findFirstRule(project, IntellijPluginRule::isPluginRule));
+              TargetFinder.getInstance()
+                  .findFirstTarget(project, IntellijPluginRule::isPluginTarget));
       config.vmParameters = currentVmOptions.getValue();
       return config;
     }
diff --git a/plugin_dev/src/com/google/idea/blaze/plugin/sync/IntellijPluginSyncPlugin.java b/plugin_dev/src/com/google/idea/blaze/plugin/sync/IntellijPluginSyncPlugin.java
index 93a27a3..33ba239 100644
--- a/plugin_dev/src/com/google/idea/blaze/plugin/sync/IntellijPluginSyncPlugin.java
+++ b/plugin_dev/src/com/google/idea/blaze/plugin/sync/IntellijPluginSyncPlugin.java
@@ -15,6 +15,7 @@
  */
 package com.google.idea.blaze.plugin.sync;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.idea.blaze.base.model.BlazeProjectData;
 import com.google.idea.blaze.base.model.primitives.LanguageClass;
@@ -39,10 +40,9 @@
  */
 public class IntellijPluginSyncPlugin extends BlazeSyncPlugin.Adapter {
 
-  @Nullable
   @Override
-  public WorkspaceType getDefaultWorkspaceType() {
-    return WorkspaceType.INTELLIJ_PLUGIN;
+  public ImmutableList<WorkspaceType> getSupportedWorkspaceTypes() {
+    return ImmutableList.of(WorkspaceType.INTELLIJ_PLUGIN);
   }
 
   @Nullable
diff --git a/plugin_dev/tests/integrationtests/com/google/idea/blaze/plugin/sync/PluginDevSyncTest.java b/plugin_dev/tests/integrationtests/com/google/idea/blaze/plugin/sync/PluginDevSyncTest.java
index 9515601..1acd4f2 100644
--- a/plugin_dev/tests/integrationtests/com/google/idea/blaze/plugin/sync/PluginDevSyncTest.java
+++ b/plugin_dev/tests/integrationtests/com/google/idea/blaze/plugin/sync/PluginDevSyncTest.java
@@ -17,10 +17,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import com.google.idea.blaze.base.ideinfo.RuleIdeInfo;
-import com.google.idea.blaze.base.ideinfo.RuleMap;
-import com.google.idea.blaze.base.ideinfo.RuleMapBuilder;
+import com.google.idea.blaze.base.ideinfo.TargetIdeInfo;
+import com.google.idea.blaze.base.ideinfo.TargetMap;
+import com.google.idea.blaze.base.ideinfo.TargetMapBuilder;
 import com.google.idea.blaze.base.model.BlazeProjectData;
+import com.google.idea.blaze.base.model.primitives.WorkspacePath;
 import com.google.idea.blaze.base.model.primitives.WorkspaceType;
 import com.google.idea.blaze.base.sync.BlazeSyncIntegrationTestCase;
 import com.google.idea.blaze.base.sync.BlazeSyncParams;
@@ -48,46 +49,46 @@
         "  //java/com/google:plugin",
         "workspace_type: intellij_plugin");
 
-    createFile(
-        "java/com/google/ClassWithUniqueName1.java",
+    workspace.createFile(
+        new WorkspacePath("java/com/google/ClassWithUniqueName1.java"),
         "package com.google;",
         "public class ClassWithUniqueName1 {}");
 
-    createFile(
-        "java/com/google/ClassWithUniqueName2.java",
+    workspace.createFile(
+        new WorkspacePath("java/com/google/ClassWithUniqueName2.java"),
         "package com.google;",
         "public class ClassWithUniqueName2 {}");
 
-    RuleMap ruleMap =
-        RuleMapBuilder.builder()
-            .addRule(
-                RuleIdeInfo.builder()
+    TargetMap targetMap =
+        TargetMapBuilder.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("java/com/google/BUILD"))
                     .setLabel("//java/com/google:lib")
                     .setKind("java_library")
                     .addSource(sourceRoot("java/com/google/ClassWithUniqueName1.java"))
                     .addSource(sourceRoot("java/com/google/ClassWithUniqueName2.java")))
-            .addRule(
-                RuleIdeInfo.builder()
+            .addTarget(
+                TargetIdeInfo.builder()
                     .setBuildFile(sourceRoot("java/com/google/BUILD"))
                     .setLabel("//java/com/google:plugin")
                     .setKind("java_import")
                     .addTag("intellij-plugin"))
             .build();
 
-    setRuleMap(ruleMap);
+    setTargetMap(targetMap);
 
     runBlazeSync(
         new BlazeSyncParams.Builder("Sync", SyncMode.INCREMENTAL)
             .addProjectViewTargets(true)
             .build());
 
-    assertNoErrors();
+    errorCollector.assertNoIssues();
 
     BlazeProjectData blazeProjectData =
         BlazeProjectDataManager.getInstance(getProject()).getBlazeProjectData();
     assertThat(blazeProjectData).isNotNull();
-    assertThat(blazeProjectData.ruleMap).isEqualTo(ruleMap);
+    assertThat(blazeProjectData.targetMap).isEqualTo(targetMap);
     assertThat(blazeProjectData.workspaceLanguageSettings.getWorkspaceType())
         .isEqualTo(WorkspaceType.INTELLIJ_PLUGIN);
 
diff --git a/proto_deps/proto_deps.jar b/proto_deps/proto_deps.jar
index 25a6ec4..1a140af 100755
--- a/proto_deps/proto_deps.jar
+++ b/proto_deps/proto_deps.jar
Binary files differ
diff --git a/testing/src/com/google/idea/testing/ServiceHelper.java b/testing/src/com/google/idea/testing/ServiceHelper.java
index 6b76692..7a8d2ce 100644
--- a/testing/src/com/google/idea/testing/ServiceHelper.java
+++ b/testing/src/com/google/idea/testing/ServiceHelper.java
@@ -16,7 +16,9 @@
 package com.google.idea.testing;
 
 import com.intellij.openapi.Disposable;
+import com.intellij.openapi.application.Application;
 import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.components.impl.ComponentManagerImpl;
 import com.intellij.openapi.extensions.ExtensionPoint;
 import com.intellij.openapi.extensions.ExtensionPointName;
 import com.intellij.openapi.extensions.Extensions;
@@ -43,6 +45,21 @@
         parentDisposable);
   }
 
+  public static <T> void registerApplicationComponent(
+      Class<T> key, T implementation, Disposable parentDisposable) {
+    Application application = ApplicationManager.getApplication();
+    if (application instanceof ComponentManagerImpl) {
+      replaceComponentInstance(
+          (ComponentManagerImpl) application, key, implementation, parentDisposable);
+    } else {
+      registerComponentInstance(
+          (MutablePicoContainer) application.getPicoContainer(),
+          key,
+          implementation,
+          parentDisposable);
+    }
+  }
+
   public static <T> void registerProjectService(
       Project project, Class<T> key, T implementation, Disposable parentDisposable) {
     registerComponentInstance(
@@ -63,4 +80,13 @@
           }
         });
   }
+
+  private static <T> void replaceComponentInstance(
+      ComponentManagerImpl componentManager,
+      Class<T> key,
+      T implementation,
+      Disposable parentDisposable) {
+    T old = componentManager.registerComponentInstance(key, implementation);
+    Disposer.register(parentDisposable, () -> componentManager.registerComponentInstance(key, old));
+  }
 }
diff --git a/testing/test_defs.bzl b/testing/test_defs.bzl
index 9fdbd90..8ee1992 100644
--- a/testing/test_defs.bzl
+++ b/testing/test_defs.bzl
@@ -91,6 +91,8 @@
     srcs,
     test_package_root,
     deps,
+    size="medium",
+    shard_count=None,
     jvm_flags = [],
     runtime_deps = [],
     platform_prefix="Idea",
@@ -107,6 +109,8 @@
     srcs: the test classes.
     test_package_root: only tests under this package root will be run.
     deps: the required deps.
+    size: the test size.
+    shard_count: the number of shards to use.
     jvm_flags: extra flags to be passed to the test vm.
     runtime_deps: the required runtime_deps.
     platform_prefix: Specifies the JetBrains product these tests are run against. Examples are
@@ -153,9 +157,10 @@
 
   native.java_test(
       name = name,
-      size = "medium",
+      size = size,
       srcs = srcs + [suite_class_name],
       data = data,
+      shard_count = shard_count,
       jvm_flags = jvm_flags,
       test_class = suite_class,
       runtime_deps = runtime_deps,
diff --git a/version.bzl b/version.bzl
index 3775bd9..ec651ae 100644
--- a/version.bzl
+++ b/version.bzl
@@ -1,3 +1,3 @@
 """Version of the blaze plugin."""
 
-VERSION = "1.11.0"
+VERSION = "1.12.6"